UOJ Round #2
http://vfleaking.blog.uoj.ac/blog/38
「UR #2」猪猪侠再战括号序列
猪猪侠大神太厉害了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include<set> #include<map> #include<ctime> #include<queue> #include<cmath> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define inf 1000000000 #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,now; char ch[200005]; int main() { scanf("%s",ch+1); n=strlen(ch+1);n>>=1; printf("%d\n",n); for(int i=1;i<=2*n;i++) if(ch[i]=='(') printf("%d %d\n",++now,i); return 0; } |
下面俩题怎么这么恶心T T
「UR #2」跳蚤公路
负环能影响一个点v当其与1,v都连通,这个用floyd就好
不等式取整要手写
虽然分析了那个式子写起来还是蛋疼
每个环每个系数k,枚举j,取整范围求并
就能得出所能影响的点的x取值范围,x<=l或x>=r
一个点的x被许多这样的取整范围限定T T
将区间排序一下扫一遍得去其范围即可。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
#include<set> #include<map> #include<ctime> #include<queue> #include<cmath> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define inf 9000000000000000000LL #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } ll $(ll a,ll b) { if(b<0)a=-a,b=-b; return a>0?a/b:-((-a+b-1)/b); } ll _(ll a,ll b) { return -$(-a,b); } int n,m,cnt; int last[105]; ll f[105][105][205]; bool mp[105][105]; vector<pair<ll,ll> >L[105],tmp; struct edge{ int to,next,v,s; }e[20005]; void insert(int u,int v,int w,int s) { e[++cnt]=(edge){v,last[u],w,s};last[u]=cnt; } void spfa() { for(int i=0;i<=n;i++) for(int j=1;j<=n;j++) for(int k=0;k<=2*n;k++) f[i][j][k]=inf; f[0][1][n]=0; for(int i=0;i<n;i++) for(int j=1;j<=n;j++) for(int k=0;k<=2*n;k++) if(f[i][j][k]!=inf) { for(int l=last[j];l;l=e[l].next) f[i+1][e[l].to][k+e[l].s]=min(f[i+1][e[l].to][k+e[l].s],f[i][j][k]+e[l].v); f[i+1][j][k]=min(f[i][j][k],f[i+1][j][k]); } } int main() { n=read();m=read(); for(int i=1;i<=m;i++) { int u=read(),v=read(),w=read(),s=read(); insert(u,v,w,s); mp[u][v]=1; } spfa(); for(int i=1;i<=n;i++)mp[i][i]=1; for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) mp[i][j]|=(mp[i][k]&mp[k][j]); for(int i=1;i<=n;i++) for(int k=0;k<=2*n;k++) if(f[n][i][k]!=inf) { ll l=-inf,r=inf; for(int j=0;j<=2*n;j++) if(f[n-1][i][j]!=inf) { if(j>k)r=min(r,_(f[n-1][i][j]-f[n][i][k],j-k)); else if(j<k)l=max(l,$(f[n-1][i][j]-f[n][i][k],j-k)); else if(f[n-1][i][j]<=f[n][i][k])l=inf,r=-inf; } if(l<=r)L[i].push_back(make_pair(l,r)); } for(int v=1;v<=n;v++) { ll l=inf,r=-inf,now=-inf; bool flag=0; tmp.clear(); for(int u=1;u<=n;u++) if(mp[1][u]&&mp[u][v]) for(int i=0;i<L[u].size();i++) tmp.push_back(L[u][i]); sort(tmp.begin(),tmp.end()); for(int i=0;i<tmp.size();i++) { if(i==0) { if(tmp[i].first>-inf) { l=-inf;r=tmp[i].first;flag=1;break; } } else if(tmp[i].first>=now) { l=now;r=tmp[i].first;flag=1;break; } now=max(now,tmp[i].second); } if(!flag&&now<inf)l=now,r=inf; if(l==-inf||r==inf||tmp.size()==0)puts("-1"); else if(l<=r)printf("%lld\n",r-l+1); else puts("0"); } return 0; } |
「UR #2」树上GCD
题意
定义树上的一条路径\(a,b\)的权值,设a为深度小的点, LCA 为\(a,b\)的最近公共祖先
若a为b的祖先,权值为\(dis(a,b)\),否则权值为\(gcd(dis(a,LCA),dis(b,LCA))\),输出路径权值为i的路径数
\(1<=i<n\)
题解
算法一
枚举两个点 \(u,v\),用 \(O(\log n)\) 时间计算 LCA 和 gcd,并更新答案。复杂度 \(O(n^2\log n)\)。
也可以枚举 LCA,往子树方向进行 bfs,由于计算 gcd 是 \(O(\log n)\)的,所以复杂度还是 \(O(n^2\log n)\)。
可以获得 10 分。
算法二
随机生成的树的树高为 \(O(\log n)\)。
枚举 u,计算以 u 为 LCA 的所有点对的答案。计算时对 \(u\) 的子树 dfs,统计出每个深度 \(h\) 上的结点数量 \(cnt[h]\)。枚举深度 \(h_1, h_2\),并将 \(cnt[h_1]\cdot cnt[h_2]\) 累加到 \(ans[\gcd(h_1,h_2)]\) 中。注意还需把同一个子树内的多余计算给减掉。这样,统计答案的复杂度是 \(O(deg[u]\cdot \log^2 n)\),总和是\(O(n \log^2 n)\)。另外,每个点最多被DFS到 \(O(\log n)\) 次,所以dfs的总复杂度是 \(O(n\log n)\)。
于是总复杂度 \(O(n\log^2 n)\)。可以获得 30 分。
算法三
我们可以把问题转化为,对于每个 d,求出 \(\gcd(h_1,h_2)\) 是 d 的倍数的点对的数量,然后用个\(O(n\log n)\)的暴力就能得出答案。
第4个数据点中,从根结点挂下来若干条链,长度分别为 \(h_1,\cdots,h_k\)。\(u,v\) 属于同一条链的情况可以方便计算;\(u,v\) 不在同一条链上时,LCA即为根结点。对于某个 d,第 i 条链中高度为 d 的倍数的点有 \(\left\lfloor h_i/d\right \rfloor\) 个;我们要统计 k 条链中选出两条来的乘积总和,可以利用恒等式 \((x_1+\cdots+x_k)^2=x_1^2+\cdots + x_k^2 +2\sum_{1\leq i < j\leq k}x_i x_j\) 求得。
其实维护前缀和就行了TAT
复杂度\(O(n\log n)\)。可以获得 40 分。
算法四
考虑点分治,每次取出当前树的中心 c。考虑所有 \(u \rightarrow LCA(u,v) \rightarrow v\) 的路径经过 c 的点对 \((u,v)\) 所产生的贡献,共有两种情况:
1.\(u,v\) 均在 c 的子树内,此时 LCA 即为 c。和算法三类似,只是第 i 条链中高度为 d 的倍数的点的数量需要在处理出 cnt 数组后花 \(hi/d\) 的时间统计,所以复杂度是\(O(n \log n)\)。
2.u 在 c 的子树内,而 v 不在。我们把当前树的根节点记为 root,\(father[c]\) 到 root 的路径为 \(father[c]=a_1,a_2,\cdots,a{k-1},a_k=root\)。记 c 下面的子树高度为 H,\(a_i\) 旁边伸出的子树(即不包含 \(a_{i-1}\) 的子树的高度为 \(h_i\)。枚举 \(a_i=LCA(u,v)\),那么 v 位于 \(a_i\) 旁边的子树中,然后我们仍然枚举 d,用 \(h_i/d\) 的时间求出 \(a_i\) 子树中高度为 d 的倍数的结点数量;但我们还需要知道 c 的子树中有多少点相对于 \(a_i\) 的高度是 d 的倍数,与前者直接相乘计入答案,也就是说我们要在 c 子树的 cnt 数组中查询下标间隔为 d 的子序列中的元素之和\((0<d<=h_i)\)。注意到间隔为 d 时,至多只有 d 种这样的子序列,我们对重复查询进行记忆化。于是对于 \(d<\sqrt{H}\),查询的复杂度不超过 \(d\cdot H/d=H\),总共是 \(\sqrt{H}\cdot H\);对于 \(d>\sqrt{H}\),单次查询的复杂度为 \(H/d<\sqrt{H}\)。
这里可能还要考虑 u 在 c 的子树内,v = \(a_i\)。由于\(a_i\)到 c 的距离是从1开始的一段区间,对于\(ans[i] (0<i<=H+dis(a_i,c))\)的贡献是 cnt 的一段区间和,随着 i 的增长这个区间左右端点单调递增,所以复杂度是\(O(n)\)。
所以一层分治的复杂度是 \(O(n\sqrt{n})\)的。根据主定理,总复杂度就是\(O(n\sqrt{n})\),可以通过所有测试点获得 100 分。
10分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#include<set> #include<map> #include<ctime> #include<queue> #include<cmath> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define inf 1000000000 #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,m,cnt; ll s[200005],s1[200005],s2[200005]; ll chain[200005],ans[200005]; int deep[200005],last[200005],b[200005]; bool mark[200005]; struct edge{ int to,next; }e[200005]; void insert(int u,int v) { e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt; } void dfs(int x) { for(int i=last[x];i;i=e[i].next) { deep[e[i].to]=deep[x]+1; dfs(e[i].to); } } void getans() { for(int i=n-1;i;i--) { ans[i]=s[i]; for(int j=i+i;j<n;j+=i) ans[i]-=ans[j]; } } int main() { n=read(); for(int i=2;i<=n;i++) { int x=read(); mark[x]=1; insert(x,i); } dfs(1); for(int i=1;i<=n;i++) if(!mark[i])b[++m]=deep[i]; for(int i=1;i<=m;i++) for(int j=1;j<=b[i];j++) chain[j]+=(b[i]-j+1); for(int i=1;i<=m;i++) for(int j=1;j<=b[i];j++) { s1[j]+=(ll)(b[i]/j)*(b[i]/j); s2[j]+=b[i]/j; } for(int i=1;i<n;i++)s2[i]=s2[i]*s2[i]; for(int i=1;i<n;i++)s[i]+=(s2[i]-s1[i])/2; getans(); for(int i=1;i<n;i++)printf("%lld\n",ans[i]+chain[i]); return 0; } |
100分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
#include<set> #include<map> #include<ctime> #include<queue> #include<cmath> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define inf 1000000000 #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,m,ed,rt,sum,top,B; int p[200005],last[200005]; int f[200005],size[200005],dep[200005],cnt[200005]; ll tmp[200005],scnt[200005],stmp[200005]; ll s[200005],ans[200005],ans2[200005]; ll F[505][505]; bool vis[200005]; struct edge{ int to,next; }e[400005]; void insert(int u,int v) { e[++ed].to=v;e[ed].next=last[u];last[u]=ed; e[++ed].to=u;e[ed].next=last[v];last[v]=ed; } void getrt(int x,int fa) { size[x]=1;f[x]=0; for(int i=last[x];i;i=e[i].next) if(!vis[e[i].to]&&e[i].to!=fa) { getrt(e[i].to,x); size[x]+=size[e[i].to]; f[x]=max(f[x],size[e[i].to]); } f[x]=max(f[x],sum-size[x]); if(f[x]<f[rt])rt=x; } void getdp(int x,int fa) { top=max(top,dep[x]);cnt[dep[x]]++; for(int i=last[x];i;i=e[i].next) if(!vis[e[i].to]&&e[i].to!=fa) { dep[e[i].to]=dep[x]+1; getdp(e[i].to,x); } } void solve(int x) { vis[x]=1; int mx=0,mx2=0; for(int i=last[x];i;i=e[i].next) if(!vis[e[i].to]&&e[i].to!=p[x]) { dep[e[i].to]=1; top=0;getdp(e[i].to,x);mx=max(mx,top); for(int j=1;j<=top;j++) for(int k=j;k<=top;k+=j) tmp[j]+=cnt[k]; for(int j=1;j<=top;j++) { scnt[j]+=cnt[j];ans2[j]+=cnt[j]; s[j]+=tmp[j]*stmp[j]; stmp[j]+=tmp[j]; cnt[j]=tmp[j]=0; } } //=========================================== 都在c的子树内 scnt[0]=1; int t=x,now=0; for(int i=p[x];!vis[i]&&i;t=i,i=p[i]) { now++;top=0; for(int j=last[i];j;j=e[j].next) if(e[j].to!=t&&!vis[e[j].to]&&e[j].to!=p[i]) { dep[e[j].to]=1; getdp(e[j].to,i); } for(int j=1;j<=top;j++) for(int k=j;k<=top;k+=j) tmp[j]+=cnt[k]; mx2=max(mx2,top); for(int j=1;j<=min(top,B);j++) { if(F[j][now%j]==-1) { F[j][now%j]=0; for(int k=(j-now%j)%j;k<=mx;k+=j) F[j][now%j]+=scnt[k]; } s[j]+=F[j][now%j]*tmp[j]; } for(int j=B+1;j<=top;j++) for(int k=(j-now%j)%j;k<=mx;k+=j) s[j]+=scnt[k]*tmp[j]; for(int j=1;j<=top;j++)cnt[j]=tmp[j]=0; ans2[now]++; } //=========================================== ai子树到c子树 int L=1,R=0; ll ans=0; for(int i=2;i<=now+mx;i++) { if(R+1<i)ans+=scnt[++R]; if(L+now<i)ans-=scnt[L++]; ans2[i]+=ans; } //=========================================== ai到c子树 for(int i=1;i<=min(B,mx2);i++) for(int j=0;j<i;j++)F[i][j]=-1; for(int i=0;i<=mx;i++)scnt[i]=stmp[i]=0; for(int i=last[x];i;i=e[i].next) if(!vis[e[i].to]) { rt=0; sum=size[e[i].to]; getrt(e[i].to,x); solve(rt); } } int main() { memset(F,-1,sizeof(F)); n=read();B=sqrt(n); for(int i=2;i<=n;i++) { p[i]=read(); insert(p[i],i); } f[rt]=inf;sum=n; getrt(1,0); solve(rt); for(int i=n-1;i;i--) { ans[i]=s[i]; for(int j=i+i;j<n;j+=i) ans[i]-=ans[j]; } for(int i=1;i<n;i++)printf("%lld\n",ans[i]+ans2[i]); return 0; } |
您居然顺手Hack掉我这么久之前过的题…. 我差点看不懂自己的程序了…
T T 我由于不理解您的代码,就构造数据看中间结果。。。然后发现了奇怪的问题
不不不… 是我以前太弱了(现在也是)… 没有判那些1无法到达的点, 还继续去做… 其实我的程序想法很简单… 就是二分二分二分…
就是把解不等式变成二分么。。。
差不多吧… 因为对于每一个点来说, 不会出现那啥balabala路径的区间是连续的. 而对于某一个具体的负环来说, 有一个分界点决定他是成为负环还是不成为负环. 所以分两层二分, 第一层看一下mid会不会有负环, 如果没有, 那好, 分左右两段进入第二层二分, 这时候有单调性了所以没问题. 如果mid有负环, 那么找出一条具体的负环, 看看可行区间是在mid的左边还是右边. 复杂度的保证需要强连通, 把不在强连通的边删掉之后复杂度嗖嗖地从n^4logR变成了n^3logR.