NOIP2018訓練題集


1. CZday3C

給定有m個數的集合,從其中任選一個子集滿足全部&后不為零,問方案數。

考慮對二進制位容斥,問題轉化為求包含某個二進制位集合的數的個數,通過類似FMT的DP求解。

 1 #include<bits/stdc++.h>
 2 #define mo 1000000007
 3 #define ll long long
 4 using namespace std;
 5 ll m,n,k,f[1100010],g[1100010],x,ans;
 6 ll po(ll x,ll y){ll z=1;while (y){if (y%2==1)z=(x*z)%mo;x=(x*x)%mo;y/=2;}return z;}
 7 int main(){
 8     freopen("loneliness.in","r",stdin);
 9     freopen("loneliness.out","w",stdout);
10     cin>>n;
11     cin>>n>>m>>k;
12     for (int i=1;i<=m;i++){scanf("%d",&x);g[x]++;}
13     f[0]=-1;
14     for (int i=0;i<(1<<n);i++)
15         for (int j=1;j<(1<<n);j*=2)
16             if ((i&j)==0)f[i+j]=f[i]*(-1);
17     for (int i=1;i<(1<<n);i*=2)
18         for (int j=0;j<(1<<n);j++)
19             if ((i&j)!=0) g[j-i]+=g[j];
20     for (int i=1;i<(1<<n);i++)ans=(ans+f[i]*po(g[i],k)+mo)%mo;
21     cout<<ans<<endl;
22     return 0;
23 }
View Code

 

2.CZday6C

在線修改,查詢第一個比前面所有數之和大的數。

類似FRBSUM,如果一個數不是答案,則下一個答案必然不小於從頭一直加到這個數的和,這樣倍增查詢每次至少翻倍,線段樹支持即可。

 1 #include<bits/stdc++.h>
 2 #define maxn 200010
 3 #define N 800010
 4 #define ll long long
 5 using namespace std;
 6 int value[maxn], mx[N];
 7 ll sum[N];
 8 void build(int now, int l, int r)
 9 {
10     if (l == r)
11     {
12         mx[now] = sum[now] = value[l];
13         return;
14     }
15     int mid = (l + r) / 2;
16     build(now * 2, l, mid);
17     build(now * 2 + 1, mid + 1, r);
18     mx[now] = max(mx[now * 2], mx[now * 2 + 1]);
19     sum[now] = sum[now * 2] + sum[now * 2 + 1];
20 }
21 void modify(int now, int l, int r, int x, int y)
22 {
23     if (l == r)
24     {
25         mx[now] = sum[now] = value[l];
26         return;
27     }
28     int mid = (l + r) / 2;
29     if (x <= mid) modify(now * 2, l, mid, x, y);
30     else modify(now * 2 + 1, mid + 1, r, x, y);
31     mx[now] = max(mx[now * 2], mx[now * 2 + 1]);
32     sum[now] = sum[now * 2] + sum[now * 2 + 1];
33 }
34 ll query_sum(int now, int l, int r, int left, int right)
35 {
36     if (l == left && r == right) return sum[now];
37     int mid = (l + r) / 2; ll ans = 0;
38     if (left <= mid) ans = query_sum(now * 2, l, mid, left, min(right, mid));
39     if (right >= mid + 1) ans += query_sum(now * 2 + 1, mid + 1, r, max(left, mid + 1), right);
40     return ans;
41 }
42 int query(int now, int l, int r, int left, int right, ll x)
43 {
44     int mid = (l + r) / 2;
45     if (l == left && r == right)
46     {
47         if (mx[now] < x) return -1;
48         if (l == r) return l;
49         if (mx[now * 2] >= x) return query(now * 2, l, mid, l, mid, x);
50         else return query(now * 2 + 1, mid + 1, r, mid + 1, r, x);
51     }
52     int ans = -1;
53     if (left <= mid) ans = query(now * 2, l, mid, left, min(right, mid), x);
54     if (ans != -1) return ans;
55     else return query(now * 2 + 1, mid + 1, r, max(left, mid + 1), right, x);
56 }
57 int main()
58 {
59     freopen("challenge.in", "r", stdin);
60     freopen("challenge.out", "w", stdout);
61     int n, q;
62     scanf("%d%d", &n, &q);
63     for (int i = 1; i <= n; i++)
64         scanf("%d", &value[i]);
65     build(1, 1, n);
66     for (int i = 1; i <= q; i++)
67     {
68         int x, y;
69         scanf("%d%d", &x, &y);
70         value[x] = y;
71         modify(1, 1, n, x, y);
72         int cur = 0, res = -1; ll pre = 0;
73         while (cur < n)
74         {
75             int tmp = query(1, 1, n, cur + 1, n, pre);
76             if (tmp == -1) break;
77             cur = tmp; pre = query_sum(1, 1, n, 1, tmp);
78             if (pre - value[cur] == value[cur]) {res = tmp; break;}
79         }
80         printf("%d\n", res);
81     }
82     return 0;
83 }
View Code

 

3.ACday3C

n個數,m個等級(m<=10),某個數不小於某值ai就是i級。在線區間加,單點修改,詢問區間所有數等級和。

線段樹,Min[x]表示這個區間中最小的離下一級的距離。更新時如果Min[x]>0則退出,否則遞歸下去。均攤復雜度nmlogn。類似區間開方之類的線段樹均攤操作。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define ls (x<<1)
 4 #define rs (ls|1)
 5 #define lson ls,L,mid
 6 #define rson rs,mid+1,R
 7 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 8 using namespace std;
 9 
10 const int N=500010;
11 int n,m,Q,op,x,y,k,a[12],c[N],sm[N],mn[N],tag[N];
12 
13 int chk(int v){ for (int i=m; ~i; i--) if (v>=a[i]) return i; return 0; }
14 void upd(int x){ sm[x]=sm[ls]+sm[rs]; mn[x]=min(mn[ls],mn[rs]); }
15 
16 void push(int x){
17     if (!tag[x]) return;
18     mn[ls]+=tag[x]; tag[ls]+=tag[x];
19     mn[rs]+=tag[x]; tag[rs]+=tag[x];
20     tag[x]=0;
21 }
22 
23 void build(int x,int L,int R){
24     if (L==R){ sm[x]=chk(c[L]); mn[x]=a[sm[x]+1]-c[L]; return; }
25     int mid=(L+R)>>1;
26     build(lson); build(rson); upd(x);
27 }
28 
29 void work(int x,int L,int R){
30     if (mn[x]>0) return;
31     if (L==R){
32         while (mn[x]<=0) sm[x]++,mn[x]=a[sm[x]+1]-a[sm[x]]+mn[x];
33         return;
34     }
35     push(x); int mid=(L+R)>>1;
36     work(lson); work(rson); upd(x);
37 }
38 
39 void add(int x,int L,int R,int l,int r,int k){
40     if (L==l && r==R){ mn[x]-=k; tag[x]-=k; work(x,L,R); return; }
41     push(x); int mid=(L+R)>>1;
42     if (r<=mid) add(lson,l,r,k);
43     else if (l>mid) add(rson,l,r,k);
44         else add(lson,l,mid,k),add(rson,mid+1,r,k);
45     upd(x);
46 }
47 
48 void mdf(int x,int L,int R,int pos,int k){
49     if (L==R){ sm[x]=chk(k); mn[x]=a[sm[x]+1]-k; return; }
50     push(x); int mid=(L+R)>>1;
51     if (pos<=mid) mdf(lson,pos,k); else mdf(rson,pos,k);
52     upd(x);
53 }
54 
55 int que(int x,int L,int R,int l,int r){
56     if (L==l && r==R) return sm[x];
57     push(x); int mid=(L+R)>>1;
58     if (r<=mid) return que(lson,l,r);
59     else if (l>mid) return que(rson,l,r);
60         else return que(lson,l,mid)+que(rson,mid+1,r);
61 }
62 
63 int main(){
64     scanf("%d%d%d",&n,&m,&Q);
65     rep(i,1,m) scanf("%d",&a[i]);
66     a[0]=-2e9; a[m+1]=2e9;
67     rep(i,1,n) scanf("%d",&c[i]);
68     build(1,1,n);
69     while (Q--){
70         scanf("%d%d%d",&op,&x,&y);
71         if (op==1) scanf("%d",&k),add(1,1,n,x,y,k);
72         else if (op==2) mdf(1,1,n,x,y);
73             else printf("%d\n",que(1,1,n,x,y));
74     }
75     return 0;
76 }
View Code

 

4.ACday4C

一棵樹和一些邊,在線詢問只取[L,R]中的邊時,S到T的邊權異或和最小的路徑。

非常好的一道題,發現一條邊的貢獻一定是和樹邊組成的環的異或和,線性基加優化求解。

http://noi.ac/contest/13/problem/41

https://www.cnblogs.com/Gloid/p/9658421.html

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
 6 using namespace std;
 7 
 8 const int N=300010;
 9 struct Que{ int id,s,l,r; }q[N],q1[N];
10 int n,m,Q,u,v,w,S,T,l,r,cnt,dep[N],d[N],ans[N],h[N],to[N<<1],val[N<<1],nxt[N<<1];
11 void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
12 
13 struct B{
14     int d[32];
15     B (){ memset(d,0,sizeof(d)); }
16     void ins(int x){
17         for (int i=30; ~i; i--)
18             if (x&(1<<i)){
19                 if (!d[i]) { d[i]=x; break; } else x^=d[i];
20             }
21     }
22 }suf[N],pre[N];
23 
24 B operator +(const B &a,const B &b){
25     B c;
26     for (int i=30; ~i; i--) c.d[i]=a.d[i];
27     rep(i,0,30) if (b.d[i]) c.ins(b.d[i]);
28     return c;
29 }
30 
31 void dfs(int x,int fa){
32     For(i,x) if ((k=to[i])!=fa) dep[k]=dep[x]^val[i],dfs(k,x);
33 }
34 
35 int work(int x,const B &a){ for (int i=30; ~i; i--) x=min(x,x^a.d[i]); return x; }
36 
37 void solve(int l,int r,int low,int high){
38     if (l>r || low>high) return;
39     if (low==high){ rep(i,l,r) ans[q[i].id]=min(q[i].s,q[i].s^d[low]); return; }
40     int mid=(low+high)>>1,st=l-1,ed=r+1;
41     for (int i=mid; i>=low; i--){
42         if (i==mid) pre[i]=pre[0]; else pre[i]=pre[i+1];
43         pre[i].ins(d[i]);
44     }
45     rep(i,mid,high){
46         if (i==mid+1) suf[i]=suf[0]; else suf[i]=suf[i-1];
47         suf[i].ins(d[i]);
48     }
49     rep(i,l,r)
50         if (q[i].l<=mid && q[i].r>mid) ans[q[i].id]=work(q[i].s,pre[q[i].l]+suf[q[i].r]);
51         else if (q[i].r<=mid) q1[++st]=q[i]; else q1[--ed]=q[i];
52     rep(i,l,r) q[i]=q1[i];
53     solve(l,st,low,mid); solve(ed,r,mid+1,high);
54 }
55 
56 int main(){
57     scanf("%d%d%d",&n,&m,&Q);
58     rep(i,2,n) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
59     dfs(1,0);
60     rep(i,1,m) scanf("%d%d%d",&u,&v,&w),d[i]=dep[u]^dep[v]^w;
61     rep(i,1,Q) scanf("%d%d%d%d",&S,&T,&l,&r),q[i]=(Que){i,dep[S]^dep[T],l,r};
62     solve(1,Q,1,m);
63     rep(i,1,Q) printf("%d\n",ans[i]);
64     return 0;
65 }
View Code

 

5.NOWday1C

一棵樹一些路徑,每次詢問一個點vi的最遠祖先u,滿足uv之間的路徑被至少ki條路徑完全包含。

可以DFS化成二維數點問題主席樹和倍增解決,也可以每個點記錄子樹內最遠的一個是某個路徑拐點(LCA)的祖先,線段樹合並即可。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #define lson ls[x],L,mid
 6 #define rson rs[x],mid+1,R
 7 #define rep(i,l,r) for (int i=l; i<=r; i++)
 8 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
 9 typedef long long ll;
10 using namespace std;
11  
12 const int N=200010;
13 int n,m,u,v,k,l,cnt,nd,Q,rt[N],fa[N][19],dep[N];
14 int h[N],nxt[N<<1],to[N<<1],sz[N*80],ls[N*80],rs[N*80];
15 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
16  
17 void dfs(int x){
18     dep[x]=dep[fa[x][0]]+1;
19     rep(i,1,18) fa[x][i]=fa[fa[x][i-1]][i-1];
20     For(i,x) if ((k=to[i])!=fa[x][0]) fa[k][0]=x,dfs(k);
21 }
22  
23 int lca(int u,int v){
24     if (dep[u]<dep[v]) swap(u,v);
25     int t=dep[u]-dep[v];
26     for (int i=18; ~i; i--) if (t&(1<<i)) u=fa[u][i];
27     if (u==v) return u;
28     for (int i=18; ~i; i--) if (fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
29     return fa[u][0];
30 }
31  
32 void ins(int &x,int L,int R,int k){
33     if (!x) x=++nd; sz[x]++;
34     if (L==R) return;
35     int mid=(L+R)>>1;
36     if (k<=mid) ins(lson,k); else ins(rson,k);
37 }
38  
39 int merge(int x,int y){
40     if (!x || !y) return x+y;
41     int p=++nd; sz[p]=sz[x]+sz[y];
42     ls[p]=merge(ls[x],ls[y]); rs[p]=merge(rs[x],rs[y]);
43     return p;
44 }
45  
46 void dfs2(int x){
47     For(i,x) if ((k=to[i])!=fa[x][0]) dfs2(k),rt[x]=merge(rt[x],rt[k]);
48 }
49  
50 int que(int x,int L,int R,int k){
51     if (L==R) return L;
52     int mid=(L+R)>>1;
53     if (k<=sz[ls[x]]) return que(lson,k); else return que(rson,k-sz[ls[x]]);
54 }
55  
56 int main(){
57     scanf("%d%d",&n,&m);
58     rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
59     dfs(1);
60     rep(i,1,m) scanf("%d%d",&u,&v),l=lca(u,v),ins(rt[v],1,n,dep[l]),ins(rt[u],1,n,dep[l]);
61     dfs2(1);
62     for (scanf("%d",&Q); Q--; ) scanf("%d%d",&v,&k),printf("%d\n",max(dep[v]-que(rt[v],1,n,k),0));
63     return 0;
64 }
View Code

 

6.NOWday2B

給換上每個位置i一個[1,ai]中的整數,任意兩個相鄰位置數不相同,求方案數。

考慮容斥,到位置i時,枚舉i與i-1,...,i-k+1都相等,轉移為$f_i=\sum\limits_{j} f_j\times min(a_{j+1..i})\times (-1)^{i-j-1}$

單調隊列優化,這里也比較巧妙,具體看代碼。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 typedef long long ll;
 5 using namespace std;
 6  
 7 const int N=2e6+7,mod=1e9+7;
 8 ll n,a[N],stk[N][3],f[N],ans,tmp,mn;
 9  
10 int main(){
11     scanf("%lld",&n); a[0]=1e9+8;
12     rep(i,1,n){
13         scanf("%lld",&a[i]);a[i+n]=a[i];
14         if(a[i]<a[mn])mn=i;
15     }
16     rep(i,1,n) a[i]=a[i+mn-1];
17     f[0]=n&1?-1:1; int t=1;
18     stk[1][0]=1e9+8; stk[1][1]=f[0];
19     rep(i,1,n){
20         int sum=0;
21         while(t&&a[i]<stk[t][0])sum=(sum+stk[t--][1])%mod;
22         f[i]=((f[i]-stk[t][2]-1ll*sum*a[i]%mod)%mod+mod)%mod;
23         stk[++t][0]=a[i];
24         stk[t][1]=sum;
25         stk[t][2]=(1ll*sum*a[i]+stk[t-1][2])%mod;
26         stk[++t][0]=1e9+8;
27         stk[t][1]=f[i];
28         ans=(ans+f[i])%mod;
29     }
30     ans=(ans+(n&1?-a[1]:a[1]))%mod;
31     printf("%lld",ans);
32     return 0;
33 }
View Code

 

7.ACday5B

長度為n的序列A,從中刪去恰好k個元素(右邊的元素往左邊移動),記cnt為新序列中Ai=i的元素個數(即權值與下標相同的元素的個數)。求cnt的最大值。

f[i]表示以i結尾的序列的最優答案,則有f[i]=max{f[j]+1} (i-j>=a[i]-a[j],a[i]>a[j])。

考慮按a從小到大的順序DP,直接樹狀數組維護即可。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 using namespace std;
 6 
 7 const int N=1000010;
 8 int n,k,ans,f[N],a[N],c[N];
 9 pair<int,int>b[N];
10 
11 void add(int x,int d){ x++; for (; x<=n; x+=x&-x) c[x]=max(c[x],d); }
12 int que(int x){ x++; int res=0; for (; x; x-=x&-x) res=max(res,c[x]); return res; }
13 
14 void work(int x){
15     if (a[x]>x) return;
16     f[x]=que(x-a[x])+1; add(x-a[x],f[x]);
17     if (a[x]>=x-k && a[x]<=n-k) ans=max(ans,f[x]);
18 }
19 
20 int main(){
21     freopen("delete.in","r",stdin);
22     freopen("delete.out","w",stdout);
23     scanf("%d%d",&n,&k);
24     rep(i,1,n) scanf("%d",&a[i]),b[i]=make_pair(a[i],-i);
25     sort(b+1,b+n+1);
26     rep(i,1,n) work(-b[i].second);
27     printf("%d\n",ans);
28     return 0;
29 }
View Code

 

8.ACday5C

一棵樹,選定一個大小為k的連通塊,將連通塊中所有點編號拿出排序,最大化最長的連續自然數段的長度。

two pointers枚舉所有相鄰幾個自然數,問題轉化為求包含這些點的最小連通塊大小。

這實際上就是求每個點到所有點的LCA的鏈並,也就是每兩個DFS序相鄰的點的路徑長度和,用經典的DFS序+set維護。

 1 #include<set>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 6 typedef long long ll;
 7 using namespace std;
 8 typedef set<int>::iterator It;
 9 
10 const int N=100010;
11 set<int>S;
12 int n,k,u,v,sm,cnt,tot,dep[N],dfn[N],b[N],fa[N][18],h[N],nxt[N<<1],to[N<<1];
13 
14 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
15 
16 void dfs(int x){
17     dfn[x]=++tot; b[tot]=x;
18     rep(i,1,17) fa[x][i]=fa[fa[x][i-1]][i-1];
19     for (int i=h[x],k; i; i=nxt[i])
20         if ((k=to[i])!=fa[x][0]) fa[k][0]=x,dep[k]=dep[x]+1,dfs(k);
21 }
22 
23 int lca(int u,int v){
24     if (dep[u]<dep[v]) swap(u,v);
25     int t=dep[u]-dep[v];
26     for (int i=17; ~i; i--) if (t&(1<<i)) u=fa[u][i];
27     if (u==v) return u;
28     for (int i=17; ~i; i--) if (fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
29     return fa[u][0];
30 }
31 
32 int dis(int u,int v){ return dep[u]+dep[v]-2*dep[lca(u,v)]; }
33 
34 void ins(int u){
35     It it2=S.lower_bound(dfn[u]);
36     if (it2==S.end()) it2=S.begin();
37     It it1=it2;
38     if (it1==S.begin()) it1=S.end(),it1--; else it1--;
39     int x=b[*it1],y=b[*it2];
40     sm+=dis(x,u)+dis(u,y)-dis(x,y);
41     S.insert(dfn[u]);
42 }
43 
44 void era(int u){
45     It it=S.lower_bound(dfn[u]),it1=it;
46     if (it1==S.begin()) it1=S.end(),--it1; else --it1;
47     It it2=it; ++it2;
48     if (it2==S.end()) it2=S.begin();
49     int x=b[*it1],y=b[*it2];
50     sm-=dis(x,u)+dis(u,y)-dis(x,y); S.erase(dfn[u]);
51 }
52 
53 int main(){
54     freopen("power.in","r",stdin);
55     freopen("power.out","w",stdout);
56     scanf("%d%d",&n,&k);
57     rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
58     dfs(1);
59     int st=1,ans=1; S.insert(dfn[1]);
60     rep(i,2,n){
61         ins(i);
62         if (sm/2+1>k) era(st++);
63         ans=max(ans,i-st+1);
64     }
65     printf("%d\n",ans);
66     return 0;
67 }
View Code

 

9.NOWday3A

n*m的矩陣,每個格子都有pij的概率為黑,每次矩形邊界上的格子和所有與黑格子相鄰的格子都會變黑,求每個格子期望在第幾次變黑。

各個事件的獨立性會影響概率方程的正確性,所以列的式子盡量不要包含別的概率。

一個思路是,由於最多會在max(n,m)次內變黑,所以求出每個格子在第i次變黑的概率即可。

但這樣仍然不好求,考慮“每個格子至少i次才能變黑”的概率。

容易發現,“每個格子至少i次才能變黑”,當且僅當所有離它曼哈頓距離不超過i的格子全是白格子。

於是問題轉化為求以每個格子為中心的菱形的概率積,直接求解即可。

最后期望就是“至少一次才能變黑的概率”+“至少兩次”+“至少三次”+...

或者根據“至少”得到“恰好”,乘以曼哈頓距離為i+1的點不全為白點的概率即可。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 using namespace std;
 5 
 6 const int N=210,mod=1e9+7;
 7 int n,m,x,y,v[N][N],ans[N][N],f1[N][N],f2[N][N];
 8 
 9 int ksm(int a,int b){
10     int res=1;
11     for (; b; a=1ll*a*a%mod,b>>=1)
12         if (b & 1) res=1ll*res*a%mod;
13     return res;
14 }
15 
16 int main(){
17     freopen("a.in","r",stdin);
18     freopen("a.out","w",stdout);
19     scanf("%d%d",&n,&m);
20     rep(i,1,n) rep(j,1,m)
21         scanf("%d%d",&x,&y),ans[i][j]=v[i][j]=1ll*x*ksm(y,mod-2)%mod;
22     rep(j,1,m){
23         rep(i,1,n){
24             int s=v[i][j]; f1[i][0]=v[i][j];
25             rep(k,1,max(n,m)){
26                 if (j-k<1 || j+k>m) break;
27                 s=1ll*s*v[i][j-k]%mod*v[i][j+k]%mod;
28                 f1[i][k]=1ll*f1[i-1][k-1]*s%mod;
29             }
30         }
31         for (int i=n; i; i--){
32             int s=v[i][j]; f2[i][0]=v[i][j];
33             rep(k,1,max(n,m)){
34                 if (j-k<1 || j+k>m) break;
35                 s=1ll*s*v[i][j-k]%mod*v[i][j+k]%mod;
36                 f2[i][k]=1ll*f2[i+1][k-1]*s%mod;
37             }
38         }
39         rep(i,1,n) rep(k,1,max(n,m)){
40             if (j-k<1 || j+k>m) break;
41             ans[i][j]=(ans[i][j]+1ll*f1[i][k]*f2[i+1][k-1]%mod)%mod;
42         }
43     }
44     rep(i,1,n){ rep(j,1,m) printf("%d ",ans[i][j]); puts(""); }
45     return 0;
46 }
View Code

 

10.NOWday3B

在一張有向無權圖(n<=5000)上找到最小的交錯環並輸出(即環上的邊的方向交錯),可以有重點但不能有重邊。

考慮拆點,每條邊從u出點連向v入點,問題立刻轉化為在無向(二分)圖上找最小環,暴力從每個點開始BFS即可。

因為可以證明:如果一個無向圖的平均度數至少是8,那么它一定包含一個環。所以bfs時只要搜索最多5000*8條邊,一定會找到環並退出。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 using namespace std;
 6 
 7 const int N=10010,M=2000010;
 8 bool vis[N];
 9 int n,m,u,v,q[N],cnt,res[N],Ans[N],h[N],to[M],nxt[M],fa[N];
10 
11 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
12 void init(){ cnt=0; memset(h,0,sizeof(h)); }
13 
14 int main(){
15     freopen("b.in","r",stdin);
16     freopen("b.out","w",stdout);
17     scanf("%*d");
18     while (1){
19         scanf("%d%d",&n,&m); init();
20         if (n==0) break;
21         rep(i,1,m) scanf("%d%d",&u,&v),add(u,v+n),add(v+n,u);
22         int ans=-1;
23         rep(i,1,n){
24             memset(vis,0,sizeof(vis));
25             memset(fa,0,sizeof(fa));
26             q[1]=i; vis[i]=1;
27             int tot=0; bool flag=0;
28             for (int st=0,ed=1; st<ed; ){
29                 if (flag) break;
30                 int x=q[++st];
31                 for (int p=h[x],k; p; p=nxt[p]){
32                     if (vis[k=to[p]] && k!=fa[x]){
33                         while (x!=i) res[++tot]=x,x=fa[x];
34                         res[++tot]=i; reverse(res+1,res+tot+1);
35                         while (k!=i) res[++tot]=k,k=fa[k];
36                         flag=1; break;
37                     }
38                     if (!vis[k]) vis[k]=1,fa[k]=x,q[++ed]=k;
39                 }
40             }
41             if (tot && (ans==-1 || tot<ans)) ans=tot,swap(Ans,res);
42         }
43         printf("%d\n",ans);
44         if (ans==-1) continue;
45         rep(i,1,ans) printf("%d ",(Ans[i]-1)%n+1);
46         puts("");
47     }
48     return 0;
49 }
View Code

 

11.NOWday3C

有一堆石子,先手可取任意數量的石子但是無法直接全部取走,此后每個人可取前一個人取走的k倍以內任意數量的石子(不能不取),取走最后一個石子的贏,問先手是否必勝。

部分分:容易得到kn的DP算法,並發現k=2時所有必敗態都在fibonacci數點上。

結論:若先手在n=n0時輸,那么找到[n0/k,n0)中最小的m0使先手輸,則先手在n=n0+m0時同樣必敗(而在(n0,n0+m0)中均必勝)。

由此可以直接找到所有必敗點。

證明:若n在(n0,n0+m0)中,則先手將石子數變為n0即可。n=n0+m0時,先手無法取m0(太大),則后手可以取m0到必勝態。得證。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 typedef long long ll;
 6 using namespace std;
 7 
 8 ll m,n,i,j,T,nw[10000010];
 9 
10 int main(){
11     freopen("c.in","r",stdin);
12     freopen("c.out","w",stdout);
13     for (scanf("%lld",&T); T--; ){
14         scanf("%lld%lld",&m,&n);
15         if(n<=m+1){ puts("DOG"); continue; }
16         for (i=1; i<=m+1; ++i) nw[i]=i;
17         j=1;
18         while (nw[i-1]<n){
19             while ((nw[i-1]-1)/nw[j]>=m) ++j;
20             nw[i]=nw[i-1]+nw[j]; ++i;
21         }
22         puts(nw[i-1]==n?"DOG":"GOD");
23     }
24     return 0;
25 }
View Code

 

12.ACday6B

四個梯子,n層每層只有一個梯子在這層有木塊,問有多少種方案,滿足存在一個梯子,任意兩個有木塊的層之間距離不超過d(第一塊要小於等於d,最后一塊要大於等於n-d)。(n<=1000,d<=30)

容易想到f[i][x][y][z][w]的DP,若一個梯子存在間隔超過d的層則直接記為d層,故狀態數是nd^4的。

考慮去掉一維,對於當前正在考慮的這層所放的梯子,只需要管它是死是活即可。狀態變為f[i][0/1][x][y][z],DP之間的轉移要考慮清楚。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 using namespace std;
 6 
 7 const int N=35,mod=1e9+9;
 8 int n,h,f[2][2][N][N][N],u,ans;
 9 
10 int cal(int x){ return min(x+1,h); }
11 void inc(int &x,int y){ x+=y; if (x>=mod) x-=mod; }
12 
13 int main(){
14     freopen("ladder.in","r",stdin);
15     freopen("ladder.out","w",stdout);
16     scanf("%d%d",&n,&h); f[u=0][1][1][1][1]=1;
17     rep(p,2,n){
18         u^=1; memset(f[u],0,sizeof(f[u]));
19         rep(i,0,h) rep(j,0,h) rep(k,0,h){
20             int t=f[u^1][0][i][j][k];
21             inc(f[u][i<h][h][cal(j)][cal(k)],t);
22             inc(f[u][j<h][cal(i)][h][cal(k)],t);
23             inc(f[u][k<h][cal(i)][cal(j)][h],t);
24             inc(f[u][0][cal(i)][cal(j)][cal(k)],t);
25             t=f[u^1][1][i][j][k];
26             inc(f[u][i<h][1][cal(j)][cal(k)],t);
27             inc(f[u][j<h][cal(i)][1][cal(k)],t);
28             inc(f[u][k<h][cal(i)][cal(j)][1],t);
29             inc(f[u][1][cal(i)][cal(j)][cal(k)],t);
30         }
31     }
32     rep(i,0,h) rep(j,0,h) rep(k,0,h)
33         inc(ans,f[u][0][i][j][k]),inc(ans,f[u][1][i][j][k]);
34     inc(ans,mod-f[u][0][h][h][h]);
35     printf("%lld\n",4ll*ans%mod);
36     return 0;
37 }
View Code

 

13.NOWday5A

 求x在[0,n],y在[0,m]且(x^y)%m=0的(x,y)個數,n,m<=1e18。

見代碼。

 1 ll solve(ll a,ll b){
 2     ll ans=0; a++; b++;
 3     rep(i,0,60) if (a&(1ll<<i))
 4         rep(j,0,60) if (b&(1ll<<j)){
 5             int k=max(i,j);
 6             ll l=((a^(1ll<<i))^(b^(1ll<<j)))&(~((1ll<<k)-1)),r=l+(1ll<<k)-1;
 7             ans=(ans+(r/m-l/m+(l%m==0))%mod*((1ll<<min(i,j))%mod))%mod;
 8         }
 9     return ans;
10 }
View Code

 

14.NOWday5C

求有多少對01串S,T,滿足:

1. S有a個0,b個1

2. T有c個0,d個1

3. T是S的子序列。

a,b,c,d<=2000

當T確定時,考慮往T上加a-c個0和b-d個1得到S。為了避免重復,規定T一定要是S的第一個長這樣的子序列。

於是每個0就只能加在T中某個1的前面,或T的末尾。1同理。

枚舉有多少個0和1不加在末尾,組合數求解,最后乘上C(c+d,c)即可。特判c=0或d=0。

rep(i,0,b) rep(j,0,a) ans+=C(c+i-1,c-1)*C(d+j-1,d-1)*C(a-c+b-d-i-j,b-d-i);

 

15.WHday1

5個大小為200的集合,要求從5個集合中各選一個使和為0,求方案數。

分成2+2+1的形式,枚舉1的那200個數,將另外兩個排序雙指針掃一下即可。

 

16.WHday2

n棵樹,每棵樹有a[i]個桃子,掉落的速度是每天d[i]個。接下來k天,每天可以將一棵樹上的桃子摘光,求最大摘桃數。(n,k<=1000)
顯然最優解中是按d從大到小摘的,那么我們將所有數按d排序后直接DP即可。

 

17.WHday3T1

n個點,每個點有兩個值s和l,當i<j且|si-sj|<=li+lj時,從i到j連一條長度為(j-i)^2的邊,求1到n的最短路。
亂搞做法:每一次從i到j顯然是j-i越小越好,那么每次只考慮從i跳到i+1,i+2,...,i+12,可得90分。
正解:顯然DP,發現每個點可以抽象成一個[si-li,si+li]的區間,兩區間有交即可連邊。於是用set維護一個區間集合。容易證明,若兩個區間有交,則交的部分一定取編號較大的一個,於是set中存的區間都是互不相交的。每次加入新的區間時,先找到set中與它相交的區間進行轉移,再用這個區間覆蓋別的區間,復雜度O(nlogn)。

 

18.WHday3T2

支持兩種操作:區間覆蓋成某種顏色,區間查詢共有多少種不同的顏色。顏色數<=30
如果是單點修改,就是經典原題,記錄每個位置的lst(即上個和它顏色相同的位置),則詢問就是求lst<l的位置的個數。
但這個題不要陷入慣性思維,由顏色數入手,使用線段樹,用二進制記錄每個區間有哪些顏色,支持區間覆蓋和或運算即可。

 

19.WHday6T1

n個數,每次詢問有多少種子集選法,使子集和為m倍數,相等數算不同選法。n<=1e5,Q<=30,m<=100。

O(nmQ)的不難想到,70分。發現每個數x和x%m是完全等價的,於是考慮將復雜度優化成O(Q(nm+m^3))。

首先用桶記錄0~m-1每個數出現的次數,然后f[i][j]表示前i個數湊成j的方案數。

顯然有f[i][j]=f[i-1][j-k*a[i]]*C(n,k),狀態數只有O(n^2),考慮去掉k對復雜度的影響。

預處理p[i][j]記錄數i對湊成j的方案數C的影響(也就是C之和),轉移時直接枚舉f[i-1][l]*p[i][i-l]即可。

 

20.WHday6T2

平面上n個點,用K個邊長為L的正方形覆蓋所有點,最小化L。n<=1e5,K<=3。

K=1直接求解。

K=2找到覆蓋所有點的最小矩形,則兩個正方形一定分別在矩形的兩個對角上,答案為max{min(max(xi-x0,yi-y0),max(x1-xi,y1-yi))}。

K=3肯定有至少一個正方形在矩形的一個角上,討論在哪個角上並二分這個正方形的邊長,問題轉化為K=2的情況。

 

21.WHday6T3

在一張有向圖上隨便走(步數可無限),第i步走到的點x的貢獻為x*r^i,求能走出的最大收益,保留一位小數。n<=100,0<r<0.999999。

先將有限情況直接算出,步數不可能超過n步。

無限情況顯然不可能精確算出,但如果能考慮往后走2^30步的最大收益,精度上就幾乎沒有誤差了。

套路:f[i][j][p]表示從i走2^p步到j的最大收益,轉移枚舉走2^(p-1)步時到的點k,則f[i][j][p]=f[i][k][p-1]+f[k][j][p-1]*(w^(2^(p-1)))。

 

22.NOWday5T1

一張有向圖上,每個點有一個字符,求從每個點出發的字典序最小的最長路。

方法一:難點主要在於比較兩條路徑的字典序,而比較只會發生在離終點距離相等的點之間,所以只要在拓撲時用優先隊列,保證同層點之間的大小關系即可。

 1 #include<queue>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 6 using namespace std;
 7  
 8 const int N=1000010,mod=998244353;
 9 int n,m,u,v,w,cnt,tot,ind[N],ans[N],rk[N],fr[N],pre[N],dis[N],h[N],to[N],nxt[N],val[N];
10 void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
11  
12 struct P{ int x,dis,pre,rk; };
13 bool operator <(const P &a,const P &b){
14     return (a.dis==b.dis) ? ((a.pre==b.pre) ? a.rk>b.rk : a.pre>b.pre) : a.dis>b.dis;
15 }
16 priority_queue<P>Q;
17  
18 void Top(){
19     rep(i,1,n) if (!ind[i]) Q.push((P){i,0,0,0}),ans[i]=0;
20     while (!Q.empty()){
21         int x=Q.top().x; Q.pop(); rk[x]=++tot;
22         if (ans[x]==-1) ans[x]=29ll*(ans[fr[x]]+pre[x])%mod;
23         for (int i=h[x]; i; i=nxt[i]){
24             int k=to[i];
25             if ((dis[k]<dis[x]+1) || ((dis[k]==dis[x]+1) && ((val[i]<pre[k]) || (val[i]==pre[k] && rk[fr[k]]>rk[x]))))
26                 dis[k]=dis[x]+1,pre[k]=val[i],fr[k]=x;
27             if (!--ind[k]) Q.push((P){k,dis[k],pre[k],rk[fr[k]]});
28         }
29     }
30 }
31  
32 int main(){
33     scanf("%d%d",&n,&m);
34     rep(i,1,m) scanf("%d%d%d",&u,&v,&w),add(v,u,w),ind[u]++;
35     memset(ans,-1,sizeof(ans)); Top();
36     rep(i,1,n) if (ans[i]==-1) puts("Infinity"); else printf("%d\n",ans[i]);
37     return 0;
38 }
View Code

方法二:hash+倍增。找出第一個不同的位置比較。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 using namespace std;
 5  
 6 typedef long long ll;
 7  
 8 const int mod=998244353,P1=31,P2=19260817;
 9 const int N=1000010;
10 int n,m,u,v,w,cnt,ans[N],dis[N],ind[N],pw[N],q[N],f[N][21],g[N][21],h[N],to[N],val[N],nxt[N];
11 void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
12  
13 bool Cmp(int x,int y){
14     for (int i=20; ~i; i--)
15         if (f[x][i] && f[y][i] && g[x][i]==g[y][i]) x=f[x][i],y=f[y][i];
16     return g[x][0]>g[y][0];
17 }
18  
19 int main(){
20     scanf("%d%d",&n,&m);
21     rep(i,1,m) scanf("%d%d%d",&u,&v,&w),add(v,u,w),ind[u]++;
22     pw[0]=P1; rep(i,1,20) pw[i]=1ll*pw[i-1]*pw[i-1]%P2;
23     int st=0,ed=0;
24     rep(i,1,n) if (!ind[i]) q[++ed]=i;
25     while (st<ed){
26         int x=q[++st];
27         rep(i,1,20) f[x][i]=f[f[x][i-1]][i-1],g[x][i]=(1ll*g[x][i-1]*pw[i-1]%mod+g[f[x][i-1]][i-1])%mod;
28         for (int i=h[x]; i; i=nxt[i]){
29             int k=to[i];
30             if (dis[x]+1>dis[k] || (dis[x]+1==dis[k] && (g[k][0]>val[i] || (g[k][0]==val[i] && Cmp(f[k][0],x)))))
31                 g[k][0]=val[i],f[k][0]=x,ans[k]=29ll*(ans[x]+val[i])%mod,dis[k]=dis[x]+1;
32             if (!--ind[k]) q[++ed]=k;
33         }
34     }
35     rep(i,1,n) if (ind[i]) puts("Infinity"); else printf("%d\n",ans[i]);
36     return 0;
37 }
View Code

 

23.求a+b<=n且a+b|ab的數對(a,b)數量,n<=1e12。

設d=gcd(a,b),a'=a/d,b'=b/d,則有(a'+b')d|a'b'd^2。

因為若a與b互質,則a+b與ab互質,故有a'+b'|d。

由a'+b'<=d且(a'+b')d<=n,得到a'+b'<=sqrt(n)。

枚舉k=a'+b',則對於每個k有n/k^2個d(a'+b'|d且d<=n/(a'+b')),和phi(k)個a。

 

24.給定數列a,多次詢問(l,r,k),回答a[l~r]中模k最大的數。n,k<=1e5。

發現對於每個k,[ak,(a+1)k)之間的最大值一定是這些數%k的最大值,這樣的區間一共klogk個。

分塊,預處理ans[i][j]表示第i塊模j的最大值。塊的大小設為sqrt(klogk)。

考慮如何計算ans,對每塊開個桶記錄不大於某個數的最大的數,然后klogk枚舉所有區間更新。

 

25.(1)[ACday9A][BZOJ4773]一張有向帶權圖,找邊數最少的負環,輸出邊數。(n<=300)

首先發現,floyd可以看作類似矩陣乘法的操作,而矩陣乘法是可以快速冪加速的,於是這個題的思路就比較清晰了。

f[l][i][j]表示從i走2^l以內步到j,能走出的最小邊權和,通過枚舉中間點轉移,然后用類似倍增LCA的方法求出答案。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 using namespace std;
 5 
 6 const int N=310,inf=1e9;
 7 int n,m,ans,u,v,w,f[10][N][N],now[N][N],tmp[N][N];
 8 
 9 void mul(int a[N][N],int b[N][N],int c[N][N]){
10     rep(i,1,n) rep(j,1,n) c[i][j]=inf;
11     rep(k,1,n) rep(i,1,n) rep(j,1,n)
12         if (a[i][k]!=inf && b[k][j]!=inf) c[i][j]=min(c[i][j],a[i][k]+b[k][j]);
13 }
14 
15 int main(){
16     freopen("bzoj4773.in","r",stdin);
17     freopen("bzoj4773.out","w",stdout);
18     scanf("%d%d",&n,&m);
19     rep(k,0,9) rep(i,1,n) rep(j,1,n) f[k][i][j]=inf;
20     rep(i,1,n) rep(j,1,n) now[i][j]=inf;
21     rep(i,1,n) f[0][i][i]=now[i][i]=0;
22     rep(i,1,m) scanf("%d%d%d",&u,&v,&w),f[0][u][v]=w;
23     rep(i,1,9) mul(f[i-1],f[i-1],f[i]);
24     for (int i=9; ~i; i--){
25         mul(now,f[i],tmp); bool flag=0;
26         rep(j,1,n) if (tmp[j][j]<0) flag=1;
27         if (!flag){
28             ans|=1<<i;
29             rep(j,1,n) rep(k,1,n) now[j][k]=tmp[j][k];
30         }
31     }
32     if (ans>n) puts("0"); else printf("%d\n",ans+1);
33     return 0;
34 }
View Code

(2)[NOW7day7C]一張有向圖無權圖,每次詢問ui到vi是否存在長度為li的路徑。(n<=100,l<=1e9)

類似的想法,f[l][i][j]表示i走2^l以內步能否到達j,每次將li二進制拆分,更新能到達的狀態集合,使用bitset優化O(n^3logn/64)。

 1 #include<bitset>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<iostream>
 6 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 7 #define For(i,x) for (int i=h[x]; i; i=nxt[i])
 8 typedef long long ll;
 9 using namespace std;
10 
11 const int N=110;
12 int n,m,u,v,Q,l;
13 bitset<N>f[31][N],to[2];
14 
15 int main(){
16     freopen("c.in","r",stdin);
17     freopen("c.out","w",stdout);
18     scanf("%d%d",&n,&m);
19     rep(i,1,m) scanf("%d%d",&u,&v),f[0][u][v]=1;
20     rep(j,1,30) rep(i,1,n) rep(k,1,n) if (f[j-1][i][k]) f[j][i]|=f[j-1][k];
21     for (scanf("%d",&Q); Q--; ){
22         scanf("%d%d%d",&l,&u,&v);
23         int p=1,q=0;
24         to[0].reset(); to[1].reset(); to[0][u]=1;
25         rep(j,0,30) if (l&(1<<j)){
26             p^=1; q^=1; to[q].reset();
27             rep(i,1,n) if (to[p][i]) to[q]|=f[j][i];
28         }
29         puts((to[q][v])?"YES":"NO");
30     }
31     return 0;
32 }
View Code

 

26.經典問題:一張無向帶權連通圖,每次詢問ui到vi是否存在一條路徑(可以走環)異或和為wi。(n<=1e5)

先求出圖的一棵生成樹,非樹邊的權值全部由val變為val^s[u][lca]^s[v][lca],其中s[i][j]為i到j的路徑異或和。

對非樹邊建線性基,每次將詢問扔進線性基查詢即可。

 

27.NOWday8C

雙人博弈,輪流操作,每次從數列兩端共選k個數刪去(k|n-1),A要使最后留下的數最大,B要使最小。A先手,求最終剩下的數。線性。

實際上要求游戲的納什均衡點。先考慮最后一步A取的情況。

考慮到若對A來說均衡點在最中間k個數中,則A一定能通過模仿B的操作(即B首選了x個,尾y個,則A反之)從而保證最后未刪的點就是這個數。而若不在最中間的k個數中,B一定能通過模仿A從而刪掉這個數打破平衡。故最終答案只要在最中間的k個數中取max即可。

若最后一步B取,則B也同理會在最中間的k個數中選出最小的,則A的最后一步決策一定是在最中間的連續2k個數中找最小值最大的一個長度為k的區間。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 4 using namespace std;
 5 
 6 const int N=1000010;
 7 int T,a[N],n,k,P,q[N];
 8 char s[N];
 9 
10 int main(){
11     for (scanf("%d",&T); T--; ){
12         scanf("%s",s); scanf("%d%d",&n,&k);
13         if (s[2]=='w') rep(i,1,n) scanf("%d",&a[i]);
14         else{
15             scanf("%d%d",&a[1],&P);
16             rep(i,2,n) a[i]=(2333ll*a[i-1]+6666)%P;
17         }
18         if ((n-1)/k&1){
19             int ans=0,mid1=(n-k+1)/2, mid2=k+(n-k+1)/2;
20             rep(i,mid1,mid2) ans=max(ans,a[i]);
21             printf("%d\n", ans);
22         }
23         else{
24             int ans=0,mid1=(n-2*k+1)/2,mid2=2*k+(n-2*k+1)/2,l=1,r=0;
25             rep(i,mid1,mid2){
26                 while (l<=r && q[l]<i-k) l++;
27                 while (l<=r && a[q[r]]>a[i]) r--;
28                 q[++r]=i;
29                 if (i>=mid1+k) ans=max(ans,a[q[l]]);
30             }
31             printf("%d\n", ans);
32         }
33     }
34     return 0;
35 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM