[7.19NOIP模拟测试6]失恋三连(雾 题解
不得不说这次的题除了引起单身汪极度不适之外还是出的很有水平的……
A.
很好的dp题
模型非常简单,如果数据范围足够友好的话就是一道dp入门题
30%:
我们可以设$dp[i][j]$为到第i天一共喂食给出了j块饼干的方案数
易得转移方程:$dp[i][j+k]=\sum \limits_{k=0}^{min(m-1,n-j)}{dp[i-1][j]}$,i枚举天数,j枚举已给出数量,k枚举下一步给出数量
$\sum \limits_{i=1}^{n}{dp[i][n]}$即为答案
但是i是要枚举到d的,我们的二维数组显然开不了这么大,所以我们可以得到30分的好成绩(滑稽
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=2005; const ll mod=998244353; int n,m; ll dp[N][N],D; void work() { if(1LL*n>=1LL*m*D||m<=1) { puts("0"); return ; } memset(dp,0,sizeof(dp)); for(int i=0;i<=min(m-1,n);i++) dp[1][i]=1; for(int i=2;i<=D;i++) for(int j=0;j<min(1LL*n,D*m);j++) for(int k=0;k<=min(m-1,n-j);k++) (dp[i][j+k]+=dp[i-1][j])%=mod; ll ans=0; for(int i=1;i<=D;i++) (ans+=dp[i][n])%=mod; cout<<ans%mod<<endl; } int main() { while(1) { scanf("%d%lld%d",&n,&D,&m); if(n==0&&D==0&&m==0)break; work(); } return 0; }
(另外,冲着30分去就按着30分范围打,不要梦想把数组开到极限还能多得分……事实上博主的30分就因为数组开太大 炸了内存 没了)
100%:
d的范围十分大,但我们不难发现真正给出饼干的天数最多只有n
所以可以把上面那个状态数组的定义稍作更改:真正给出饼干天数为i,给出饼干数量为j时的方案数
另外,为了进一步优化复杂度,使用前缀和优化,我们需要对枚举变量的意义作改动,让等号左侧为$dp[i][j]$
$dp[i][j]=\sum \limits_{k=j-m+1}^{j-1}{dp[i-1][k]}$
在统计结果时,对于每个$dp[i][n]$,是从d天中选任意i天给出饼干,所以还要乘上相应组合数
即:$ans=\sum {dp[i][n]*C_d^i}$
组合数直接根据相邻两项关系递推算即可 记得不能直接除 要乘逆元
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=2005; typedef long long ll; const ll mod=998244353; ll dp[N][N],n,d,m,fac[N],C[N],sum[N]; ll qpow(ll a,ll b) { ll res=1; while(b) { if(b&1)res=res*a%mod; a=a*a%mod; b>>=1; } //if(res<0)cout<<"Jackpot!"<<endl; return res; } ll inv(ll x) { return qpow(x,mod-2); } void work() { if(n>=m*d||m<=1) { puts("0"); return ; } memset(dp,0,sizeof(dp)); memset(sum,0,sizeof(sum)); C[0]=1; for(int i=1;i<=min(n,d);i++) { C[i]=(C[i-1]*(1LL*(d-i+1)%mod))%mod*inv(i)%mod; /* if(C[i]<0)while(1); cout<<"***"<<C[i]<<endl;*/ } for(int i=1;i<m;i++) dp[1][i]=1,(sum[i]=sum[i-1]+dp[1][i])%=mod; for(int i=m;i<=n;i++) sum[i]=sum[i-1]; for(int i=2;i<=n;i++) { for(int j=1;j<=n;j++) dp[i][j]=(sum[j-1]-sum[max(0LL,j-m)]+mod)%mod; // sum[0]=0; for(int j=1;j<=n;j++) (sum[j]=sum[j-1]+dp[i][j])%=mod; } ll ans=0; for(int i=1;i<=min(d,n);i++) (ans+=dp[i][n]*C[i]%mod)%=mod; cout<<ans<<endl; } int main() { while(1) { scanf("%lld%lld%lld",&n,&d,&m); if(!n&&!d&&!m)break; work(); } return 0; }
B.
数据很水,水到直接把这道题变成了最短路的板子……
枚举与1节点相连的点,对于每个点,把1和他们之间的边临时断开后跑最短路
用$dis[1]+len[i]$更新ans即可
正解似乎是什么二进制分组blablabla 我也不会啊
#include<cstdio> #include<iostream> #include<cstring> #include<queue> #define pa pair<int,int> using namespace std; const int N=10005,M=40005; int n,m,T; int to[M<<1],dis[N],vis[N],nxt[M<<1],tot,head[N],del[M<<1],len[M<<1]; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } void add(int x,int y,int z) { to[++tot]=y; len[tot]=z; nxt[tot]=head[x]; head[x]=tot; } void dj(int s) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pa> q; dis[s]=0;//vis[s]=1; q.push(make_pair(0,s)); while(!q.empty()) { int x=q.top().second; q.pop(); if(vis[x])continue; vis[x]=1; for(int i=head[x];i;i=nxt[i]) { if(del[i])continue; int y=to[i]; if(dis[y]>dis[x]+len[i]) { dis[y]=dis[x]+len[i]; q.push(make_pair(-dis[y],y)); } } } /*for(int i=1;i<=n;i++) cout<<dis[i]<<' '; puts(" ");*/ } void ini() { for(int i=1;i<=n;i++)head[i]=0; for(int i=1;i<=m*2;i++)to[i]=nxt[i]=del[i]=len[i]=0; tot=1; } void work() { n=read();m=read(); ini(); for(int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); add(x,y,z);add(y,x,z); } int ans=0x3f3f3f3f;; for(int i=head[1];i;i=nxt[i]) { int y=to[i]; del[i]=del[i^1]=1; dj(y); del[i]=del[i^1]=0; ans=min(ans,dis[1]+len[i]); } cout<<(ans==0x3f3f3f3f?-1:ans)<<endl; return ; } int main() { T=read(); while(T--)work(); return 0; }
(如果要使用$i\ xor\ 1$查询反边这种操作的话,链式前向星的tot初值要设成1!!!一时nc又少30!!)
C.
鬼能想到正解的神题……
首先我们考虑对这个模型进行转化。我们把2n个数字看作节点,把每张卡牌看作
是连接两个点的有向边。
于是我们的问题可以等价转化为这一问题:把图中的某些边反向使得任何一个节点
至多被一条边指向。
我们很容易想到整张图可能会被分为若干个联通块。
首先我们考虑联通块构成了树的情况。我们发现我们可以直接dp
然后我们考虑联通块构成了基环外向树的情况。我们发现只要确定环上的边的指向,
后面的外向树部分的指向就可以确定了。对于环上面的指向问题,我们发现一共只有两种
指向的可能,枚举即可。
随后我们考虑更复杂的情况,n个点,n+1条边的情况,因为我们知道每一条边肯定
会至少指向一个点,所以由抽屉原理可以一定存在一个点被指向多次,所以一定不可行。
所以,我们可以发现我们需要处理的即一个基环外向树和树的森林。
而联通块之间互不影响,分别处理即可。
树的情况需要换根进行dp。
#include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<algorithm> using namespace std; typedef long long ll; const int N=200015; const ll mod=998244353; int T,n,to[N<<1],nxt[N<<1],head[N],tot=1; int vis[N],v[N],f[N],g[N],edge,node; int cire,pos,poss; vector<int> sor; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } void add(int x,int y) { to[++tot]=y; nxt[tot]=head[x]; head[x]=tot; } void dfs(int x) { vis[x]=1;node++; for(int i=head[x];i;i=nxt[i]) { edge++; if(vis[to[i]])continue; dfs(to[i]); } } void dp1(int x,int fa) { v[x]=1; for(int i=head[x];i;i=nxt[i]) { if(to[i]==fa)continue; if(!v[to[i]]) { dp1(to[i],x); f[x]+=f[to[i]]+(i&1); } else pos=x,poss=to[i],cire=i; } } void dp2(int x,int fa) { sor.push_back(g[x]); for(int i=head[x];i;i=nxt[i]) { if(to[i]==fa)continue; if(i==cire||i==(cire^1)) continue; int contr=((i&1)?-1:1); g[to[i]]=g[x]+contr; dp2(to[i],x); } } void ini() { for(int i=1;i<=n*2;i++) vis[i]=v[i]=f[i]=g[i]=head[i]=0; for(int i=1;i<=n*4;i++) nxt[i]=to[i]=0; tot=1;edge=node=0; } void work() { n=read();ini(); for(int i=1;i<=n;i++) { int x=read(),y=read(); add(y,x);add(x,y); } /*for(int i=2;i<=tot;i++) cout<<'!'<<to[i]<<' '<<nxt[i]<<' '<<endl;*/ for(int i=1;i<=n;i++) if(!vis[i]) { node=edge=0; dfs(i); if((edge>>1)>node) { puts("-1 -1"); return ; } } int ans1=0,num=0;ll ans2=1; for(int i=1;i<=(n<<1);i++) { if(v[i])continue; sor.clear(); num=cire=pos=poss=0; dp1(i,0);g[i]=f[i]; dp2(i,0); if(!cire) { sort(sor.begin(),sor.end());//puts("QAQ"); for(int j=0;j<sor.size();j++) { //cout<<'!'<<sor[j]<<endl; if(sor[j]!=sor[0])break; num++; } ans1+=sor[0];//cout<<"***"<<ans1<<endl; } else { cire=(cire&1);//puts("QwQ"); if(g[pos]+(cire^1)==g[poss]+cire)num=2; else num=1; ans1+=min(g[pos]+(cire^1),g[poss]+cire);//cout<<"###"<<ans1<<endl; } (ans2*=1LL*num%mod)%=mod; } /*puts("---"); for(int i=1;i<=n*2;i++) cout<<f[i]<<' '<<g[i]<<endl;puts("---");*/ cout<<ans1<<' '<<ans2<<endl; } int main() { T=read(); while(T--)work(); return 0; }