GDOI2018 Day1 题目总结

T1:农场

题意:有一个长为 $n$ 的序列 $a$,要求将其分成尽可能多的部分,使得每一部分的 $a_i$ 的和相等。求最多能分成的部分数。

$30\%:1\le n\le 1000$

$80\%:1\le n\le 10^5$

$100\%:1\le a_i\le 10,1\le \sum a_i\le 10^6$

这题不难,说一下我在考场的思路:

首先答案应该是 $\sum a_i$ 的约数。那么可以转化一下,变成找到满足要求的最小的和(也是其约数)

进一步想到前缀和。我们发现 $x$ 满足条件,当且仅当 $x,2x,3x\dots$ 全部在前缀和中出现。

于是考场上写了个80分的暴力 $O(n\sqrt{n})$(枚举约数 $O(\sqrt{n})$,判断 $O(n)$)

后来发现可以做到更快:因为总和不超过 $10^6$,因此可以开桶。复杂度 $O(\sigma(n))<O(n\log n)$。

但是 $O(n\sqrt{n})$ 可以过?

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,sum;
 4 int fac[1010],fl;
 5 bool vis[1000100];
 6 void split(int x){    //求出总和的所有约数
 7     for(int i=1;i*i<=x;i++)
 8         if(x%i==0){
 9             fac[++fl]=i;
10             if(i*i!=x) fac[++fl]=x/i;
11         }
12     sort(fac+1,fac+fl+1);
13 }
14 int main(){
15     scanf("%d",&n);
16     for(int i=1;i<=n;i++){
17         int a;
18         scanf("%d",&a);
19         sum+=a;
20         vis[sum]=true;    //对前缀和开桶
21     }
22     split(sum);
23     for(int i=1;i<=fl;i++){
24         bool flag=true;
25         for(int j=fac[i];j<=sum;j+=fac[i])    //判断是否满足
26             if(!vis[j]){    //不满足
27                 flag=false;break;
28             }
29         if(flag){
30             printf("%d\n",sum/fac[i]);return 0;    //答案为总和/单个和=段数
31         }
32     }
33 }
View Code

 


T2:密码锁

题意:有一个长为 $n$ 的序列 $a$,每一次操作可以让区间 $[l,r]$ 间所有 $a_i$ 加一或所有 $a_i$ 减一 $\pmod m$。问最少多少次操作可以让序列变为全 $0$。

测试点 1~4:$1\le n\le 4,2\le m\le 10$

测试点 5~9: $1\le n\le 10^5,2\le m\le 3$

测试点 10~15:$2\le n,m\le 3000$

测试点 16~18:$2\le n\le 2\times 10^5$

全部20个测试点:$1\le n\le 10^6,2\le m\le 10^9$

这题很有思维难度。像我这种考场上连20分BFS都没想到,写了个 $\sum\min(a_i,m-a_i)$ 的……居然还有5分

看到区间加减操作,想到差分(设为 $d,d_i=(a_i-a_{i-1})\operatorname{mod}m(i\le n),d_{n+1}=m-a_n$)。

把 $a_{i-1}$ 进行 $d_i$ 次加操作后可以变成 $a_i$,或者进行 $m-d_i$ 次减操作后可以变成 $a_i$。

所以想到一个贪心:优先对 $d_i$ 小的进行加操作,剩下的进行减操作。

而对一个区间 $[l,r]$ 进行 $x$ 次操作实际上就是 $d_l-=x,d_{r+1}+=x$,整个序列变为 $0$ 就是 $d$ 变为全 $0$。

又因为每次操作都是一加一减,所以对 $d$ 进行的加操作次数等于减操作次数。

那么问题的本质就是把 $d_i$ 进行分组,使得第一组的 $\sum d_i$ 等于第二组的 $\sum m-d_i$。此时答案也就是这个相等的和。

结合刚刚那个贪心的想法,只需要把 $d_i$ 排序,枚举断点 $p$,分成 $i\le p$ 和 $i>p$ 两组。当这两组的和相等时这个和就是答案。

直接枚举断点再求和还不够,加上前缀和和后缀和就可以了。复杂度 $O(n\log n)$。

注:$d_{n+1}=m-a_n$ 一定也要考虑,否则会导致 $[l,n]$ 的操作没有意义。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 int n,m,a[1000100],dif[1000100];    //dif是差分
 5 ll sum,pre[1000100];    //pre是差分排序后的前缀和
 6 int main(){
 7     scanf("%d%d",&n,&m);
 8     for(int i=1;i<=n;i++){
 9         scanf("%d",a+i);
10         dif[i]=(a[i]-a[i-1]+m)%m;
11     }
12     dif[n+1]=(m-a[n])%m;    //注意,这个不能漏
13     sort(dif+1,dif+n+2);
14     for(int i=1;i<=n+1;i++) pre[i]=pre[i-1]+dif[i];    //做前缀和
15     for(int i=n+1;i>=1;i--){
16         sum+=m-dif[i];    //做后缀和
17         if(sum==pre[i-1]){    //前缀和和后缀和相等,这就是答案
18             printf("%lld\n",sum);return 0;
19         }
20     }
21 }
View Code

 


T3:涛涛摘苹果

题意:有一棵 $n$ 个节点的苹果树,根节点为 $1$,初始每个点都有一个重为 $a_i$ 的苹果。每天早上根节点的苹果会消失。每天下午所有苹果都会下落一层(也就是下落到它的父亲节点上)。某些天(第 $t$ 天)的晚上编号 $x$ 的点上会多长出一个重为 $w$ 的苹果,这样的操作共有 $m$ 个。某些天(第 $t$ 天)早晨根节点苹果消失前会询问以 $x$ 为根的子树中所有苹果的重量之和,共有 $q$ 个询问。

$10\%:$ 所有输入数据都不超过 $5000$

另 $10\%:$ 树退化成一条链,其中 $i$ 与 $i-1$ 有边

另 $20\%:$ 所有询问满足 $x=1$

另 $20\%:m=0$

$100\%:$ 所有输入数据都不超过 $10^5$

先考虑 $m=0$ 怎么做:

我们发现苹果 $x$ 对第 $t$ 天在节点 $y$ 的询问产生贡献,当且仅当:($sz[x]$ 表示 $x$ 的子树大小,$dfn[x]$ 表示 $x$ 的dfs序,$dep[x]$ 表示 $x$ 的深度)

$dep[x]\ge dep[y]+t-1$

$dfn[y]\le dfn[x]\le dfn[y]+sz[y]-1$

那么就可以乱搞了。(既然不是正解,不说做法了,其实是我不会

$m\ne 0$ 时,根据条件 $1$,我们发现添加的苹果 $x$ 相较其它苹果少下落了 $t$ 天,可以想象从 $x$ 新连出一个节点 $y$,使得 $dep[y]=dep[x]+t$,也就是从 $x$ 连出一条权 $t$ 的边。

根据条件 $2$,他应该在子树内,连出一条边恰好符合要求。

但这样不够,还有一个时间限制!只有添加操作的 $t\le$ 询问操作的 $t-1$,才能产生贡献。

现在是个三维偏序,CDQ即可。

时间复杂度:$O((n+m+q)\log^2(n+m+q))$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int maxn=100010;
 5 #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
 6 #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
 7 inline int read(){
 8     char ch=getchar();int x=0,f=0;
 9     while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
10     while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
11     return f?-x:x;
12 }
13 int n,m,q,cnt;
14 int el,head[maxn*2],to[maxn*3],w[maxn*3],nxt[maxn*3];
15 int dep[maxn*2],sz[maxn*2],dfn[maxn*2];
16 ll ans[maxn],bit[maxn*2];
17 struct oper{    //tp=0表示修改,tp=1表示询问,tim表示时间,x表示节点,w表示权值(可能没有),id表示原编号
18     int tp,tim,x,w,id;
19 }op[maxn*3];
20 inline void add(int u,int v,int w_){
21     to[++el]=v;w[el]=w_;nxt[el]=head[u];head[u]=el;
22 }
23 inline bool cmptim(const oper &o1,const oper &o2){    //注意只有修改会对询问产生贡献,所以时间一样的要把修改放前面
24     if(o1.tim!=o2.tim) return o1.tim<o2.tim;
25     return o1.tp<o2.tp;
26 }
27 inline bool cmpdep(const oper &o1,const oper &o2){
28     return dep[o1.x]>dep[o2.x];
29 }
30 inline bool cmphhh(const oper &o1,const oper &o2){    //各种排序
31     return dep[o1.x]+o1.tim>dep[o2.x]+o2.tim;
32 }
33 inline void modify(int p,int v){
34     for(;p<=n+m;p+=p&-p) bit[p]+=v;
35 }
36 inline ll query(int p){
37     ll ans=0;for(;p;p-=p&-p) ans+=bit[p];return ans;
38 }
39 inline ll query(int l,int r){
40     return query(r)-query(l-1);
41 }    //以上树状数组
42 void dfs(int u,int f){    //预处理dep,dfn,sz 
43     sz[u]=1;dfn[u]=++cnt;
44     for(int i=head[u];i;i=nxt[i]){
45         int v=to[i];
46         if(v==f) continue;
47         dep[v]=dep[u]+w[i];
48         dfs(v,u);sz[u]+=sz[v];
49     }
50 }
51 void CDQ(int l,int r){    //CDQ分治 
52     if(l==r) return;
53     int mid=(l+r)>>1;
54     CDQ(l,mid);CDQ(mid+1,r);
55     sort(op+l,op+mid+1,cmpdep);    //左边按照dep排序 
56     sort(op+mid+1,op+r+1,cmphhh);    //右边按照dep+time排序 
57     int cl=l;
58     FOR(i,mid+1,r){
59         for(;cl<=mid && dep[op[cl].x]>=dep[op[i].x]+op[i].tim-1;cl++)    //cl能对i产生贡献 
60             if(op[cl].tp==1) modify(dfn[op[cl].x],op[cl].w);    //是修改,插入树状数组 
61         if(op[i].tp==2) ans[op[i].id]+=query(dfn[op[i].x],dfn[op[i].x]+sz[op[i].x]-1);    //是询问,添加贡献 
62     }
63     FOR(i,l,cl-1) if(op[i].tp==1) modify(dfn[op[i].x],-op[i].w);    //清空树状数组 
64 }
65 int main(){
66     n=read();m=read();q=read();
67     FOR(i,1,n) op[i]=(oper){1,1,i,read(),0};    //一开始的苹果也可以看成是添加操作 
68     FOR(i,1,n-1){
69         int u=read(),v=read();
70         add(u,v,1);add(v,u,1);    //边权为1 
71     }
72     FOR(i,1,m){
73         int t=read(),x=read(),w=read();
74         add(x,i+n,t);    //连虚边 
75         op[i+n]=(oper){1,t+1,i+n,w,0};    //添加操作(把时间设为t+1会更方便排序) 
76     }
77     FOR(i,1,q){
78         int t=read(),x=read();
79         op[i+n+m]=(oper){2,t,x,0,i};    //询问操作 
80     }
81     dfs(1,0);
82     sort(op+1,op+n+m+q+1,cmptim);    //按时间排序 
83     CDQ(1,n+m+q);
84     FOR(i,1,q) printf("%lld\n",ans[i]);
85 }
View Code

T4:小学生图论题

题意:给出一个 $n$ 个点的有向竞赛图。有 $m$ 条链,第 $i$ 条长 $k_i$,依次经过 $a_1,a_2,\dots,a_{k_i}$。这些链没有公共点。链上所有边的方向已知,为链经过的方向,其他边方向等概率随机。求这个图强联通分量的期望个数对 $998244353$ 取模。

测试点 1~2:$1\le n\le 1000,m=0$

测试点 3~4:$1\le n,m\le 1000$

测试点 5:$k_i=2$

全部10个测试点:$1\le n,m\le 10^5,2\le k_i\le n,\sum k_i\le n$

不会做。

有朝一日会做了再来填坑吧……

posted @ 2018-12-15 15:04  ATS_nantf  阅读(479)  评论(0编辑  收藏  举报