UOJ Rounds

UOJ Test Round #1

  T1:数字比大小的本质是按(长度,字典序)比大小。

  T2首先发现单调性,二分答案,用堆模拟,$O(nlog^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
",S,res,pp,T);
54             //printf("%d %d %d
",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
",ans);
73     rep(i,1,n) printf("%lld ",Ed[i]);
74     return 0;
75 }
UTR#2 T2

  T3:毒瘤类中心。

UOJ Easy Round #1

  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'条边形成的生成树的权值和,这个用维护一个数组即可。

UOJ Round #1

  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,查询满足$txleq a_i < (t+1)x$的i的个数,然后给z[x]加上t和个数的积,查询开个桶用前缀和完成。

    这样根据调和级数就可以做到$O(Xlog 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:毒瘤仙人掌。

UOJ Round #2

  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 +2sum_{1leq i < jleq 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个。那么我们可以在$dleq sqrt{H}$时用一个数组记录答案,大于时直接统计,因为单次询问复杂度已经不会超过$O(sqrt{H})$了。

      根据主定理,每层处理复杂度已经超过了$O(n^{log_b a})$,所以总复杂度就是$f(n)=O(nsqrt{n})$。

    正解二:启发式合并。

      同样是分块,对于$dleq 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
",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
",ans[i]+cnt[i]);
42     return 0;
43 }
解法二

        

原文地址:https://www.cnblogs.com/HocRiser/p/9076017.html