1. 程式人生 > >GDOI2018 Day1 題目總結

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$

不會做。

有朝一日會做了再來填坑吧……