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){View Code9 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 }
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$
不會做。
有朝一日會做了再來填坑吧……