T1:那一天我们许下约定
n块饼干,d天内分完,每天分的块数小于m,求方案数。
( n,m<=2000 , d<=1012 )
1:dp ,设计状态发 f[i][j] 表示前i天分j块的方案数,f[i][j]= ∑ f[i-1][k] ( j-m < k < j )
发现复杂度为 O ( nd ) ,由于d过大,复杂度爆炸,30分再见;
发现实际中状态大多数是冗余的(因为好多天都不会分饼干)
所以更改状态设计,f[i][j] 表示前 i 个分了饼干的天,总共分出 j 块的方案数。
答案为 ∑ f[i][n]*C(d,i)
复杂度 O ( n2 )
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<vector> 7 #define ll long long 8 using namespace std; 9 const int MAXN=2005,D=998244353; 10 ll n,m,d,ans; 11 12 ll f[MAXN][MAXN],g[MAXN][MAXN]; 13 14 ll fac[MAXN],inv[MAXN]; 15 16 ll qpow(ll x,ll k) 17 { 18 ll ret=1; 19 while(k) 20 { 21 if(k&1) ret=(ret*x)%D; 22 x=(x*x)%D; 23 k>>=1; 24 } 25 return ret%D; 26 } 27 28 ll C(ll x,ll y) 29 { 30 ll tmp=1; 31 for(int i=0;i<y;i++) 32 tmp=(tmp*((d-i)%D))%D; 33 return (tmp*inv[y])%D; 34 } 35 36 int main() 37 { 38 while(1) 39 { 40 scanf("%lld%lld%lld",&n,&d,&m); 41 if(!n&&!m&&!d) break; 42 43 for(int i=0;i<=n;i++) g[0][i]=1; 44 for(int i=1;i<=min(n,d);i++) 45 { 46 for(int j=1;j<=n;j++) 47 if(j-m>=0) f[i][j]=((g[i-1][j-1]-g[i-1][j-m])%D+D)%D; 48 else f[i][j]=g[i-1][j-1]; 49 for(int j=1;j<=n;j++) 50 g[i][j]=(g[i][j-1]+f[i][j])%D; 51 } 52 53 fac[0]=inv[0]=fac[1]=1; 54 for(int i=2;i<=n;i++) 55 fac[i]=(fac[i-1]*i)%D; 56 inv[n]=qpow(fac[n],D-2); 57 for(int i=n-1;i>=1;i--) 58 inv[i]=(inv[i+1]*(i+1))%D; 59 60 ans=0; 61 for(int i=1;i<=min(n,d);i++) 62 ans=(ans+f[i][n]*C(d,i))%D; 63 printf("%lld\n",ans); 64 memset(f,0,sizeof(f)); 65 memset(g,0,sizeof(g)); 66 } 67 return 0; 68 }
2:机房某大佬 TKJ 指出,这不就是组合加容斥吗?
简单说一下
枚举分饼干的天数,利用1中的优化可将复杂度优化到 O ( n )
先插板法计算如果没有每天小于m的限制的答案。
再容斥计算出有m限制的答案。
具体不细说了
详情见大佬TKJ的博客:zto TKJ orz
T2:那一天她离我而去
n个点m条边的无向图,求经过点1的最小环,保证没有重边、自环
( n,m ≤ 104 )
一脸懵比?
floyd最小环??
n3 过 一万???
于是爆0,gg
1:先想暴力怎么做 ( floyd )
因为必须经过1节点,所以这个环的路径应该是1->x---最短路--->y->1(x与y均与1相连)
哦,那吼了,暴力枚举和1相连的点,全跑一遍不经过1的最短路
复杂度O ( n2logm),继续爆炸
怎么优化?
仔细一想会发现刚刚暴力枚举点会有很多冗余
所以从枚举这里优化
首先想如果把所有与1相连的点分成两组,其中一组为起点,另一组为终点
(在跑最短路时,将所有起点的距离初值设为它到1的距离,最后用所有终点的 [最短路值 + 到1的距离] 来更新答案)
这样我们就有了一种对于两组点之间的计算方法
这时我们只要考虑如何将这些与1相连的点分成两组,且对于任意两点,一定会被至少一次分在两个不同的组中
哦,然后就是一种船新的思路——二进制拆分
我们把所有与1相连的点标号
然后枚举标号二进制下的每一位,将该位为0的分为一组,1的分为另一组
因为对于任意两个点,它们的标号一定不同,所以一定至少一次被分到两个组中
所以正确,那就没了。
复杂度为 O ( n logm logn )
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 #include<queue> 7 #include<vector> 8 using namespace std; 9 const int MAXN=10005,MAXM=40005; 10 int T; 11 int n,m,ans; 12 13 struct node 14 { 15 int to,nxt,dis; 16 }mp[MAXM*2]; 17 int h[MAXN],tot; 18 void add(int x,int y,int z) 19 { 20 mp[++tot].nxt=h[x]; 21 mp[tot].to=y; 22 mp[tot].dis=z; 23 h[x]=tot; 24 } 25 26 inline int R() 27 { 28 int a=0;char c=getchar(); 29 while(c>'9'||c<'0') c=getchar(); 30 while(c>='0'&&c<='9') a=a*10+c-'0',c=getchar(); 31 return a; 32 } 33 34 int vec[MAXN],vd[MAXN],cnt,s[MAXN],sd[MAXN],ps,t[MAXN],td[MAXN],pt; 35 36 int dis[MAXN]; 37 bool vis[MAXN]; 38 39 void dijikstra() 40 { 41 memset(vis,0,sizeof(vis)); 42 memset(dis,0x3f,sizeof(dis)); 43 priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q; 44 for(int i=1;i<=ps;i++) 45 q.push(make_pair(sd[i],s[i])),dis[s[i]]=sd[i]; 46 vis[1]=1; 47 while(!q.empty()) 48 { 49 int u=q.top().second; q.pop(); 50 if(vis[u]) continue; 51 vis[u]=1; 52 for(int i=h[u];i;i=mp[i].nxt) 53 { 54 int v=mp[i].to,ds=mp[i].dis; 55 if(vis[v]) continue; 56 if(dis[v]>dis[u]+ds) 57 { 58 dis[v]=dis[u]+ds; 59 q.push(make_pair(dis[v],v)); 60 } 61 } 62 } 63 for(int i=1;i<=pt;i++) 64 ans=min(ans,dis[t[i]]+td[i]); 65 } 66 67 void first() 68 { 69 ans=0x3f3f3f3f; 70 cnt=tot=0; 71 memset(h,0,sizeof(h)); 72 } 73 74 int main() 75 { 76 T=R(); 77 while(T--) 78 { 79 first(); 80 n=R();m=R(); 81 for(int i=1,aa,bb,cc;i<=m;i++) 82 { 83 aa=R();bb=R();cc=R(); 84 add(aa,bb,cc); 85 add(bb,aa,cc); 86 } 87 for(int i=h[1];i;i=mp[i].nxt) 88 vec[++cnt]=mp[i].to,vd[cnt]=mp[i].dis; 89 90 if(!cnt) 91 { 92 printf("-1\n"); 93 continue; 94 } 95 96 for(int i=1,j=1;j<=15;i<<=1,j++) 97 { 98 ps=pt=0; 99 for(int j=1;j<=cnt;j++) 100 if(j&i) s[++ps]=vec[j],sd[ps]=vd[j]; 101 else t[++pt]=vec[j],td[pt]=vd[j]; 102 if(!ps||!pt) continue; 103 dijikstra(); 104 } 105 if(ans!=0x3f3f3f3f) printf("%d\n",ans); 106 else printf("-1\n"); 107 } 108 return 0; 109 }
T3:哪一天她能重回我身边
给定n张牌,每张牌正反面分别有一个数字,通过反转使所有正面的数字各不相同,求最小反转次数和方案数
( n <= 105 数字<=2*n )
1:不知为什么想到建图
然后在牌正反面的数字间连边,会惊奇的发现反转卡牌等价与反转边的方向,而结果是要使所有点的出度都小于等于1
原图应为多个联通块
考虑一个联通块,会发现只有边数小于等于点数才会有解
所以只可能是树或基环树
发现一种合法的反转应该是使所有的边都指向父亲 (基环树是所有的边都指向环,环上的边同为顺时针或逆时针)
于是想到换根dp
第一次dp出以任意点u的答案 (只需要计算让所有边都指向父亲需要反转多少次)
第二次dp出以每个点为根的答案
取最小值,统计最小值的方案次数
特别的: 基环树的方案只可能有1或两种,只需要判断顺逆时针的反转次数是否相同即可
(具体来说只需要判断以环上相邻两点分别为根的答案是否相等即可)
最后将每个联通块的最小值相加,方案数相乘
复杂度为 O ( n )
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<vector> 7 #define ll long long 8 using namespace std; 9 const int MAXN=100100,D=998244353; 10 int T; 11 int n; 12 13 struct node 14 { 15 int to,nxt; 16 }mp[MAXN*2]; 17 int h[MAXN*2],tot; 18 void add(int x,int y) 19 { 20 mp[++tot].nxt=h[x]; 21 mp[tot].to=y; 22 h[x]=tot; 23 } 24 25 bool flag; 26 int vcnt,ecnt; 27 28 bool vis[MAXN*2]; 29 30 void dfs(int u) 31 { 32 vcnt++;vis[u]=1; 33 for(int i=h[u];i;i=mp[i].nxt) 34 { 35 int v=mp[i].to; 36 ++ecnt; 37 if(vis[v]) continue; 38 dfs(v); 39 } 40 } 41 42 int s,nxt,id; 43 ll num,f[MAXN*2],g[MAXN*2],ans,anscnt; 44 45 void dfs1(int u,int fa) 46 { 47 vis[u]=1; 48 for(int i=h[u];i;i=mp[i].nxt) 49 { 50 int v=mp[i].to; 51 if(v==fa) continue; 52 if(vis[v]) 53 s=u,nxt=v,id=i; 54 else 55 { 56 dfs1(v,u); 57 f[u]+=f[v]+(i&1); 58 } 59 } 60 } 61 62 vector<int> vv; 63 64 void dfs2(int u,int fa) 65 { 66 vv.push_back(g[u]); 67 for(int i=h[u];i;i=mp[i].nxt) 68 { 69 int v=mp[i].to; 70 if(v==fa) continue; 71 if(i==id||i==(id^1)) continue; 72 g[v]=g[u]+((i&1)?-1:1); 73 dfs2(v,u); 74 } 75 } 76 77 int main() 78 { 79 scanf("%d",&T); 80 while(T--) 81 { 82 tot=1;flag=0; 83 memset(h,0,sizeof(h)); 84 memset(vis,0,sizeof(vis)); 85 scanf("%d",&n); 86 for(int i=1,aa,bb;i<=n;i++) 87 { 88 scanf("%d%d",&aa,&bb); 89 add(bb,aa);add(aa,bb); 90 } 91 for(int i=1;i<=2*n;i++) 92 if(!vis[i]) 93 { 94 vcnt=ecnt=0; 95 dfs(i); 96 if(ecnt/2>vcnt) 97 { 98 printf("-1 -1\n"); 99 flag=1; 100 break; 101 } 102 } 103 if(flag) continue; 104 memset(vis,0,sizeof(vis)); 105 memset(f,0,sizeof(f)); 106 memset(g,0,sizeof(g)); 107 ans=0;anscnt=1; 108 for(int i=1;i<=2*n;i++) 109 if(!vis[i]) 110 { 111 vv.clear(); 112 s=nxt=id=0; 113 num=0; 114 115 dfs1(i,0); 116 g[i]=f[i]; 117 dfs2(i,0); 118 119 if(!id) 120 { 121 sort(vv.begin(),vv.end()); 122 for(int j=0;j<vv.size();j++) 123 if(vv[j]!=vv[0]) break; 124 else ++num; 125 ans+=vv[0]; 126 } 127 else 128 { 129 id&=1; 130 if(g[s]+(id^1)==g[nxt]+id) num=2; 131 else num=1; 132 ans+=min(g[s]+(id^1),g[nxt]+id); 133 } 134 anscnt=anscnt*num%D; 135 } 136 printf("%lld %lld\n",ans,anscnt); 137 } 138 return 0; 139 }