UOJ Rounds
T1:数字比大小的本质是按(长度,字典序)比大小。
T2:首先发现单调性,二分答案,用堆模拟,$O(n\log^2 n)$。
第二个log已经没有什么可优化的了,但是第一个可以做到线性。
我们先将特殊题的p就当作是-1跑一边,设这个题的出现时间是tx,完成所需时间为sx,记录下每个题在[tx,T]上的出现时间。把所有题按优先级排序,可以发现如果找到了前i个满足出现时间之和为sx,那么这些时间区间正好可以被特殊题区间覆盖,找到这个i就确定了优先级,最后再模拟一遍即可。
有个很容易忽视的问题,就是一定要保证得到的i是能让px尽量小的,这样才可以在总时间相同的情况下使特殊题最后做完,从而保证特殊题的完成时间就是T而不是T之前。
当然按上述做法把优先级从低到高排序没有任何问题,但是如果从高到低排序最后相减就会出错了。
为了这个问题调了一整个下午。
1 #include<map> 2 #include<cstdio> 3 #include<queue> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=l; i<=r; i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=300010; 10 const ll inf=1000000000000000000ll; 11 int n,pp,pos,s[N],sm[N]; 12 ll mx,S,T,Ed[N]; 13 struct P{ int t,s,p,id; }a[N]; 14 priority_queue<P>Q; 15 map<ll,bool>mp; 16 17 bool operator <(const P &a,const P &b){ return a.p<b.p; } 18 bool cmp(const P &a,const P &b){ return (a.t==b.t) ? a.p>b.p : a.t<b.t; } 19 bool cmp1(int x,int y){ return a[x].p<a[y].p; } 20 void F(int id,ll l,ll r){ 21 l=max(l,S); r=min(r,T); 22 if (r>l) sm[id]+=r-l; 23 } 24 25 int main(){ 26 scanf("%d",&n); mp[0]=1; 27 rep(i,1,n){ 28 scanf("%d%d%d",&a[i].t,&a[i].s,&a[i].p); 29 if (a[i].p==-1) S=a[i].t,pp=a[i].s; 30 mp[a[i].p]=1; a[i].id=i; 31 } 32 scanf("%lld",&T); 33 sort(a+1,a+n+1,cmp); 34 rep(i,1,n) s[i]=i; 35 sort(s+1,s+n+1,cmp1); 36 for (int i=1,j=1; i<=n; i=j){ 37 for (; a[j].t==a[i].t; j++) Q.push(a[j]); 38 ll tim=a[i].t,ed=a[j].t; 39 if (ed==0) ed=inf; 40 while (!Q.empty()){ 41 P x=Q.top(); Q.pop(); 42 if (tim+x.s<=ed){ 43 F(x.id,tim,tim+x.s); tim+=x.s; mx=max(mx,tim); 44 if (tim==ed) break; 45 }else { x.s-=ed-tim; F(x.id,tim,ed); mx=max(mx,ed); Q.push(x); break; } 46 } 47 } 48 ll res=0,ans=1; 49 while (mp.count(ans)) ans++; 50 rep(i,1,n){ 51 res+=sm[a[s[i]].id]; 52 if (res==pp){ 53 //printf("%lld %lld %d %lld\n",S,res,pp,T); 54 //printf("%d %d %d\n",a[s[i]].p,a[s[i+1]].p,a[s[i+2]].p); 55 ans=(~a[s[i]].p) ? a[s[i]].p : a[s[i-1]].p; 56 while (mp.count(ans)) ans++; 57 break; 58 } 59 } 60 rep(i,1,n) if (a[i].p==-1) { a[i].p=ans; break; } 61 while (!Q.empty()) Q.pop(); 62 for (int i=1,j=1; i<=n; i=j){ 63 for (; a[j].t==a[i].t; j++) Q.push(a[j]); 64 ll tim=a[i].t,ed=a[j].t; 65 if (ed==0) ed=inf; 66 while (!Q.empty()){ 67 P x=Q.top(); Q.pop(); 68 if (tim+x.s<=ed) Ed[x.id]=tim+x.s,tim+=x.s; 69 else { x.s-=ed-tim; Q.push(x); tim=ed; break; } 70 } 71 } 72 printf("%lld\n",ans); 73 rep(i,1,n) printf("%lld ",Ed[i]); 74 return 0; 75 }
T3:毒瘤类中心。
T1:均值不等式或极大极小值定理直接出解。注意:
精度问题
有人可能会写:
ans_min = (long long)sqrt((double)g * l);
这样会被卡精度,因为double大概只有15位10进制有效数字。只能得到60分。
解决方法是:
ans_min = (long long)sqrt(l / g) * g;
当然有人可能直接long double保平安了……
T2:Trie树上放些指针就好了。
T3:实际上就是一个可持久化并查集,然后整个状态空间形成了一棵树,用树上倍增即可。
考虑更简便的做法。
首先如果只有Add操作,MST的形态是不会变的。
如果没有Return操作。Delete直接用可撤销并查集即可(不要路径压缩,因为代价是均摊而不是严格)。
有了Return之后的难点就在于前两种操作可以保证的均摊复杂度分析失效,我们就这一点处理:
考虑一个Return操作,如果前面是Add,那这就是一个Delete 1。
考虑一个Delete,可不可以做到,如果后面的操作不是Return,我们就“真删”,否则“假删”呢?
思考如何“假删”,就是回答前k'条边形成的生成树的权值和,这个用维护一个数组即可。
T1:最小化sum{ a[i]%x+a[i]/x },变形成 sum{ a[i]-a[i]/x *(x-1) }。
我们枚举x,问题就变成了对每个x求sum{ a[i]/x },这个设为z[x]。
从a[]的角度思考,考虑每个a[i]对数组z的影响,由于a[i]/x的值只有$O(\sqrt{a_i})$个,总复杂度就可以做到根号级别了。
从x的角度思考,枚举a[i]/x的所有值t,查询满足$tx\leq a_i < (t+1)x$的i的个数,然后给z[x]加上t和个数的积,查询开个桶用前缀和完成。
这样根据调和级数就可以做到$O(X\log X)$了,感觉很巧妙。
还有一种角度,就是每次加一个后缀和:http://uoj.ac/submission/241036
启示:很多时候从a%b=a-(a/b)*b考虑很有用。以及根号优化和调和级数很有用。
T2:又是一道好题,很像[PKUWC2018]随机算法。
这两道题的共同点在于都是要求找到一个排列使解最优,并求出最优排列的个数,以及排列的每个元素是否会其作用根据排列而改变。
首先发现a%b在b<=a时一定会其作用,在b>a时一定不会起作用。
首先考虑如何求出最优解,由于上面的结论,我们可以将a[]排序然后DP,这样实际上枚举的是排列的哪些元素起了作用,显然这样可以保证包含了最优解。
然后难点在于求出最优解的个数。
我们用f[x]表示到当前为止值为x,只考虑a[i]<x的情况,的方案数。(显然a[i]<x的a[i]是不可能在之前出现的),枚举a[i],可以从f[x%a[i]]转移过来。
这样,转移量就是大于a[i]而不大于x的那些元素的位置,乘上组合数即可。
还有一种更为精妙的方法:http://uoj.ac/submission/243023
延续起先的思路,将a[]排序,f[i][x]表示只考虑前i个中会起作用的元素并作用于原数之后,这个数会变成x的方案数,直接转移。
T3:毒瘤仙人掌。
T1:构造题,不要求最优,只要m<=n即可。
构造题还有一个套路,你可以人为固定最后变成什么样子,这样就好做多了。
比如这题,我们规定最后是(((...((()))...)))这样的,前n个全是左括号。
这个怎么实现呢,从左往右扫,扫到一个右括号时,找到它右边第一个左括号,然后把这一段翻转(由于这两个中间一定全是右括号,所以实际上相当于只交换了这两位)。
拿一个指针指向要找的左括号,显然指针单调移动,故总复杂度为线性。
T2:我不行啊。
T3:一道非常好的题目,就是很难写。
先总结一下主定理(Master Theorem):
https://blog.csdn.net/lanchunhui/article/details/52451362
https://www.cnblogs.com/SBSOI/p/5640663.html
几个表示法:o小于,Θ等于,O小于等于,Ω大于等于。
主要就是:如果f(n)是$n^{\log_{b}a}$的低阶,则结果就是$Θ(n^{\log_{b}a})$,如果同阶,则为$Θ(n^{\log_{b}a}\log n)$,如果更高阶,在一定情况下就是$Θ(f(n))$。
先看随机树部分:满足树高期望为$O(\log n)$,所以每次开一个桶,统计的复杂度是子树高度的平方,总复杂度为$O(n \log^2 n)$
然后是链的部分:根据这个式子可以方便求出不同链之间的答案(实际上记录一个后缀和也行):$(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$
然后是正解一:点分治。
首先有一个巧妙的转化方式:对于与gcd有关的题目,可以先作莫比乌斯变换方便统计,最后再莫比乌斯反演回去。这部分的复杂度都是$O(n \log n)$的。
找到树的中心c,考虑点对(u,v),如果都在c的子树中那么可以直接统计。
对于u在c子树内,v在c的祖先的其它子树内的情况,我们可以这样做:
先找到c一直到根的所有祖先,然后求出它们的其它子树的深度数组,然后和c的子树合并计数。
这样就有一个问题,每次可能会询问c的子树内的所有距离c为d的倍数的点的个数。我们发现对于相同的d,不同的序列只会有d个。那么我们可以在$d\leq \sqrt{H}$时用一个数组记录答案,大于时直接统计,因为单次询问复杂度已经不会超过$O(\sqrt{H})$了。
根据主定理,每层处理复杂度已经超过了$O(n^{\log_b a})$,所以总复杂度就是$f(n)=O(n\sqrt{n})$。
正解二:启发式合并。
同样是分块,对于$d\leq \sqrt{n}$的部分直接统计,大于的部分用vector记录答案并启发式合并,最后反演回去即可。
总结:1.认真分析复杂度。2.Mobius反演的思想。 3.树上启发式合并和线段树合并。 4.分块与记忆化的思想。
1 #include<cmath> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l),_=(r); i<=_; i++) 6 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 7 typedef long long ll; 8 using namespace std; 9 10 const int N=200010,M=500,inf=1000000000; 11 int n,dd,nd,rt,z,sq,S,h[N],f[N],sz[N],d[N],dep[N]; 12 int dp[M][M],ds[N],s1[N],fa[N],cnt[N],to[N<<1],nxt[N<<1]; 13 bool vis[N]; 14 ll ans[N],s2[N]; 15 void add(int u,int v){ to[++nd]=v; nxt[nd]=h[u]; h[u]=nd; } 16 17 void getrt(int x,int fa){ 18 sz[x]=1; f[x]=0; 19 For(i,x) if ((k=to[i])!=fa && !vis[k]) 20 getrt(k,x),sz[x]+=sz[k],f[x]=max(f[x],sz[k]); 21 f[x]=max(f[x],S-sz[x]); 22 if (f[x]<f[rt]) rt=x; 23 } 24 25 void getdep(int x,int fa){ 26 dep[x]=dep[fa]+1; 27 if (dd<dep[x]) d[dd=dep[x]]=0; 28 d[dep[x]]++; 29 For(i,x) if (!vis[k=to[i]] && k!=fa) getdep(k,x); 30 } 31 32 ll ask(int x,int y){ 33 ll res=0; 34 if (x<=sq && dp[x][y]) return dp[x][y]; 35 for (int i=y; i<=z; i+=x) res+=ds[i];//祖先的子树的深度数组(未做变换) 36 if (x<=sq) dp[x][y]=res; 37 return res; 38 } 39 40 void work(int x){ 41 dep[x]=0; z=0; 42 rep(i,0,sz[x]+1) s1[i]=s2[i]=ds[i]=0; 43 For(i,x) if (!vis[k=to[i]] && k!=fa[x]){ 44 dd=0; getdep(k,x); z=max(z,dd); 45 rep(j,1,dd) ds[j]+=d[j]; 46 rep(j,1,dd) for (int l=j+j; l<=dd; l+=j) d[j]+=d[l];//Mobius变换 47 rep(j,1,dd) s1[j]+=d[j],s2[j]+=1ll*d[j]*d[j]; 48 } 49 rep(i,1,z) ans[i]+=(1ll*s1[i]*s1[i]-s2[i])>>1; 50 sq=sqrt(z); memset(dp,0,sizeof(dp[0])*(sq+5)); 51 for (int i=x,y=1; fa[i] && !vis[fa[i]]; i=fa[i],y++)//枚举祖先的其它子树 52 For(j,fa[i]) if (!vis[k=to[j]] && k!=i && k!=fa[fa[i]]){ 53 dd=0; dep[fa[i]]=0; getdep(k,fa[i]); 54 rep(l,1,dd) for (int p=l+l; p<=dd; p+=l) d[l]+=d[p]; 55 rep(l,1,dd) ans[l]+=1ll*d[l]*(ask(l,l-y%l)+(y%l==0));//可能重心也是一个合法点 56 } 57 } 58 59 void solve(int x){ 60 vis[x]=1; 61 For(i,x) if (sz[k=to[i]]>sz[x]) sz[k]=S-sz[x]; 62 work(x); 63 For(i,x) if (!vis[k=to[i]]) S=sz[k],f[rt=0]=inf,getrt(k,x),solve(rt); 64 } 65 66 int main(){ 67 freopen("gcd.in","r",stdin); 68 freopen("gcd.out","w",stdout); 69 scanf("%d",&n); 70 rep(i,2,n) scanf("%d",&fa[i]),add(fa[i],i),add(i,fa[i]),cnt[dep[i]=dep[fa[i]]+1]++; 71 for (int i=n-1; i; i--) cnt[i]+=cnt[i+1]; 72 S=n; f[rt=0]=inf; getrt(1,0); solve(rt); 73 for (int i=n-1; i; i--) for (int j=i+i; j<n; j+=i) ans[i]-=ans[j];//Mobius反演 74 rep(i,1,n-1) printf("%lld\n",ans[i]+cnt[i]);//加上u==v的个数 75 return 0; 76 }
1 #include<cmath> 2 #include<cstdio> 3 #include<vector> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=l; i<=r; i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=200010; 10 int n,m,fa[N],dep[N],cnt[N],to[N],g[N],f[N]; 11 ll ans[N]; 12 vector<int>V[N]; 13 14 int main(){ 15 freopen("gcd.in","r",stdin); 16 freopen("gcd.out","w",stdout); 17 scanf("%d",&n); m=sqrt(n); 18 rep(i,2,n) scanf("%d",&fa[i]),cnt[dep[i]=dep[fa[i]]+1]++; 19 rep(i,1,n) to[i]=i; 20 for (int i=n; i; i--) cnt[i]+=cnt[i+1]; 21 rep(d,1,m){ 22 for (int i=n; i>1; i--){ 23 f[i]++; g[to[i]]+=f[i]; 24 ans[d]+=1ll*f[fa[i]]*g[i]; f[fa[i]]+=g[i]; 25 } 26 rep(i,1,n) to[i]=fa[to[i]],f[i]=g[i]=0; 27 } 28 for (int i=n; i; i--){ 29 V[i].push_back(1); 30 if (V[i].size()>V[fa[i]].size()) swap(V[i],V[fa[i]]); 31 int x=V[i].size(),y=V[fa[i]].size(); 32 if (x>m) rep(d,m+1,x){ 33 int a=0,b=0; 34 for (int j=d; j<=x; j+=d) a+=V[i][x-j]; 35 for (int j=d; j<=y; j+=d) b+=V[fa[i]][y-j]; 36 ans[d]+=1ll*a*b; 37 } 38 rep(j,1,x) V[fa[i]][y-j]+=V[i][x-j]; 39 } 40 for (int i=n; i; i--) for (int j=i+i; j<=n; j+=i) ans[i]-=ans[j]; 41 for (int i=1; i<n; i++) printf("%lld\n",ans[i]+cnt[i]); 42 return 0; 43 }