2019-7-19 考试总结
A. 那一天我们许下约定
根据题目很容易得到$dp$式子:
用$F[i][j]$表示第$i$次取,取了(也可以是剩下)的$j$个饼干(可以不取)的方案数。
$F[i][j]=\sum \limits_{k=j-m+1}^{j} F[i-1][k]$,
最后统计答案。
这是一个暴力的打法。
正解的$dp$定义是$F[i][j]$表示取$i$次,取了$j$个饼干(不可以不取)的方案数。
$F[i][j]=\sum \limits_{k=j-m+1}^{j-1} F[i-1][k]$,
答案$ans=\sum F[i][n] \times C_d^i$,
也就是在$d$天中选出$i$天来给饼干,然后给完。
最后就是那个$C_d^i$怎么求了。
底数不变求组合数,
$C_d^i=\frac{d!}{i!(d-i)!}$,
$C_d^{i-1}=\frac{d!}{(i-1)!(d-i+1)!}$,
上边的式子比上下边的式子,得到比值是$\frac{d-i+1}{i}$,
所以就可以$O(n)$求组合数了。
丑陋的代码:
#include<iostream> #include<cstring> #include<cstdio> #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) #define inv(x) (mi(x,mod-2,mod)) #define Maxn 2010 #define mod 998244353 #define Reg register using namespace std; long long n,m,d,anp; long long C[Maxn],ans[Maxn][Maxn],sum[Maxn][Maxn]; long long mi(long long x,long long y,long long p) { long long ans=1,base=x; while(y) { if(y&1) ans=(ans*base)%p; base=(base*base)%p; y>>=1; } return ans%mod; } int main() { while(scanf("%lld%lld%lld",&n,&d,&m)==3) { if(!n&&!m&&!d) break; memset(ans,0,sizeof(ans)); memset(sum,0,sizeof(sum)); memset(C,0,sizeof(C)); --m; C[0]=1; for(Reg int i=1;i<=min(n,d);++i) C[i]=C[i-1]*(1ll*(d-i+1)%mod)%mod*inv(i)%mod; for(Reg int i=1;i<=m;++i) { ans[1][i]=1; sum[1][i]=(sum[1][i-1]+ans[1][i])%mod; } for(Reg int i=m+1;i<=n;++i) sum[1][i]=sum[1][i-1]; anp=ans[1][n]*C[1]%mod; for(Reg int i=2;i<=n;++i) { for(Reg int j=1;j<=n;++j) { ans[i][j]=(1ll*sum[i-1][j-1]-sum[i-1][max(0,j-m-1)]+mod)%mod; sum[i][j]=(1ll*sum[i][j-1]+ans[i][j])%mod; } anp=(anp+ans[i][n]*C[i])%mod; } printf("%lld\n",anp); } return 0; }
B. 那一天她离我而去
正解是删掉与节点$1$相连的一条边然后跑最短路,
之后就是裸的最短路。
题解的下几句话每太看懂。
丑陋的代码:
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #define min(x,y) ((x)<(y)?(x):(y)) #define nex(x) ((x)%2==0?((x)-1):((x)+1)) #define Maxn 10050 #define INF 0x7ffffffffff #define Reg register using namespace std; int t,n,m,tot,fir[Maxn]; bool vis[Maxn]; long long dis[Maxn]; struct Tu {int st,ed,next; long long val;} lian[Maxn*8]; void add(int x,int y,long long z) { lian[++tot].st=x; lian[tot].ed=y; lian[tot].val=z; lian[tot].next=fir[x]; fir[x]=tot; return; } void init() { tot=0; for(Reg int i=1;i<=n;++i) fir[i]=vis[i]=0; return; } void spfa(int x) { memset(dis,0x7f,sizeof(dis)); queue<int> q; q.push(x); vis[x]=1; dis[x]=0; while(!q.empty()) { int x=q.front(); q.pop(); vis[x]=0; for(Reg int i=fir[x];i;i=lian[i].next) { if(dis[lian[i].ed]>dis[x]+lian[i].val) { dis[lian[i].ed]=dis[x]+lian[i].val; if(!vis[lian[i].ed]) { vis[lian[i].ed]=1; q.push(lian[i].ed); } } } } return; } int main() { scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); init(); long long z; for(Reg int i=1,x,y;i<=m;++i) { scanf("%d%d%lld",&x,&y,&z); add(x,y,z); add(y,x,z); } long long p,ans=INF*100; for(Reg int i=fir[1];i;i=lian[i].next) { p=lian[i].val; lian[i].val=lian[nex(i)].val=INF; spfa(lian[i].ed); ans=min(ans,dis[1]+p); lian[i].val=lian[nex(i)].val=p; } if(ans<INF) printf("%lld\n",ans); else printf("-1\n"); } return 0; }
总结:
考试开始时看了一眼$T1$,发现$T1$的$dp$转移很简单。
花了$10$分钟时间打了暴力,然后看了一眼数据范围。。。
$d$的范围很大,$10^{12}$,我下意识的想到矩阵乘,矩阵乘可以转移,
然后码了一个矩阵乘的代码,最后发现它可能是一个类似循环矩阵的矩阵,然后试了一下发现确实可以。
最后时间复杂度$O(n^2logn)$,和暴力拿的是一个分。
正解居然是组合数。。。
$T2$看起来像一个水题,求一个最小环,
考试时跑的是一个$dfs$,最后$TLE60$,考试之后$rank1$讲的是$IDA^{*}$,试了一下可以水到$90$,
正解就是删边然后跑最短路,之后好像是一个优化。
$T3$看了一眼没什么思路,然后打了一个暴力$TLE20$,
考试最后一个小时想到了好几个可能的解法,一开始想的就是要让正反连边,但是之后想的是拓扑,偏了。。
第二种解法我想的是二分图。。。。不说了。。。
但是好像我想的都不能统计方案数。。。
最后$30+60+20=110$。
加油。。。