【WHUST 2016 Individual Contest #1】解题报告
2016-07-25 13:18
666 查看
第一次个人赛自我感觉还好,毕竟抱着大腿们都要完虐我的心态下,很快地把签到题都搞出来了。ACM主要是靠脑子,我这种卡壳常客居然一鼓作气前7题写出来了到第八个题才卡……但是一卡就是三个小时……还是自己的问题,队友都在刷专题搞大新闻,我就默默写点读题解题思路好了,但愿我们队组队赛时我也能有这个水平吧。(育肥君要加油哦)
思路:上来就想类似归并和快排都是NlogN级别的交换,题目要求的是N次以内的一种交换,肯定不能直接在排序过程里下手。不管怎么样,我们可以快排很轻松地得到结果,于是问题变成了怎么从一个数列变成另一个已知数列:很显然,我们从1到N每个数都换到该在的位置(先处理1位置,如果1位置上不是答案1位置上的数,那么把现在1位置的数跟答案1位置的数进行一次交换,再去处理2位置……依此类推)因为每次让一个数归位要交换一次,所以至多交换了N次,Problem Solved.
思路:一开始做完了A看到这个题除了枚举并没有什么想法……就去做其它签到题了。回过头来一想,其实你不需要枚举四个点,你要找的是所有方案数,其实这个图就可以翻译为:两条从a出来可以两步到达c的路径。这样的基础上,我们枚举起点a,记录a走两步(dfs两层)到所有点的的路的条数,计算C(2)(k)就好了。
思路:%%%wrd.我只想到了dp,然而直接dp是不可以的,你每次A/B形式的答案上分子分母都要加值(A+x)/(B+y),这就无法保证局部最优性,也就是说走到第i个落脚点的最优解不一定比次优解好(好 是指往后走答案更优)。——该怎么办呢?比赛中就战略性放弃了……实际上这是个0/1分数dp,太久没写了都忘完了……在分子分母都在变的情况下,我们可以选择二分答案:如果我们有个答案ans,那么对式子进行变换:∑( sqrt( abs(x[i]-x[j]-L) ) - ans*b[i] )=0。如果有方案能找出更小的ans,那么等号应该变为大于号——同时,这个式子是满足局部最优性的,记录dp[i]=dp[j]+sqrt(abs(x[i]-x[j]-L))-ans*b[i],使dp[i]最小就能最终得出dp
,也为最小,如果dp
>0,那么ans不满足条件,要更大;dp
<0,试图找更小的ans,要更小……最后要求的是路径,加一个数组记录i状态是从哪个j状态转移过来的,这样回溯过去就好了。
思路:又是思路不清晰没写出来的题,遗憾。限定每行每列都是两个1,其实每两列都是可以互换的,也就是说按行去考虑,当前填充到第i行时完全可以把填充了0个1的列移到一起,把填充了1个1的列移到一起,填充了2个1的列移动到一起,于是dp[i][j][k]表示填充i行有j列有1个1,有k列有0个1(N-j-k列已经有2个1)的方案数。其中i是一行一行的,可以用滚动数组压缩一维。思路难点在于想到列可以移动而且移动后还是可以记录方案数的。
E - BerSU Ball(CodeForces 489B)
题意:二分图上只对权值差小于等于1的点连边,问最大匹配数。
思路:好裸的二分图最大匹配——等等,这题并不需要二分图——贪心地从最小的值开始匹配,如果能匹配上就匹配,如果不能匹配上就让小一点的那个走开,队列里下一个继续匹配……嗯,就这样。
思路:就是个模拟,最小的时候从最高位开始填100...99,最大的时候从最高位开始填999...00,最初还WA了一发。。。因为特判长度为1,数字总和为0.。。。Should've pay more attention to details.
思路:按N个数从小到大排序后,第i个数字会在个数为j的集合中被计算C(j-1)(N-i)次,所以j为奇数时,
j为偶数时
嗯,就酱紫。
思路:有10^5组数据而且每次给的N都是10^18以内,所以只能拿位数一位一位模拟。首先判断是不是奇数位,如果是偶数位,挨位填4还是7。要注意的细节是:
1.记录一个overwhelm代表是否前面的位已经比给定值大了,如果overwhelm==1那么有4取4,没4取7。
2.不止要记录4的个数,也要记录7的个数。当你填7填的不够了,要退回去把最近的一个4换成7,再从这个4换成7的位重新往后填。
然而,这题根本不需要这么麻烦的模拟——%%%gss。把10^18以内的所有47“幸运数”记下来就够了,每次直接在set里lowerbound就好了。orz
给出自己又长又丑的模拟代码。
思路:不能组成三角形这个太严格了,三角形最长边要小于等于另两条边,也就是说如果当前集合里最大的是a1,a2,只有a3>=a1+a2才能添加到集合中去。于是逆向思维,最少有1,有2,因为钢筋个数尽可能多就添加最小的1+2=3进去,再添加2+3=5,3+5=8...斐波那契数列了。。。找到小于前n项和小于N的最大n就好了。
思路:得分是每次操作去给的,我当时居然没有反应过来……一看就是脑子太久不用迟钝了。GG
每次取都是从大的去取,而且一定不会留下相同的数字,于是预处理一下合并相同数字。
ans[i]代表取到第i个数字,两人差最大为多少。
有ans[i]=max(ans[i-1],a[i]-ans[i-1]);//ans[i]==ans[i-1]时是把第i个数跟之前的一起取掉,ans[i]==a[i]-ans[i-1]是只取当前数字,那么相当于在i-1时是敌手的先手。
嗯-m-,这样。
思路:写过类似的,可惜忘记了具体思路。OI老龄痴呆选手TAT……M和N都只有20,很明显不是状压就是网络流(或者奇怪的暴力)。
正解是最小费用最大流,建边时要注意到一个性质,在同一个员工的等待队伍里有k个人,第i个人要被等待k-i次(所有i后面的人都在等),于是倒过来,让最后一个顾客作为第一个顾客考虑,倒数第二个顾客作为第二个顾客考虑……这样的好处是,建边时,第i个在等待的人的边权是i*等待时间,这样400+个点跑最小费用最大流就搞定了。
(补题时WA了一发,因为模板是没有多组数据的,在改的时候清理没有清理干净p[0]数组……)XD
这大概就是第一次吧(???)。题读起来还是觉得不舒服……CF跟HDU的英文题一看就能区分出来哪个是中国人出的题……不能怪出题人,要怪就怪自己思路跟出题人不一样,说明我还不是一个够格的ACMer。那。。。加油。
A - SwapSort(CodeForces 489A)
题意:问一个数列使用小于N次交换变换成排序后的数列的一个方案。(N<=3000)思路:上来就想类似归并和快排都是NlogN级别的交换,题目要求的是N次以内的一种交换,肯定不能直接在排序过程里下手。不管怎么样,我们可以快排很轻松地得到结果,于是问题变成了怎么从一个数列变成另一个已知数列:很显然,我们从1到N每个数都换到该在的位置(先处理1位置,如果1位置上不是答案1位置上的数,那么把现在1位置的数跟答案1位置的数进行一次交换,再去处理2位置……依此类推)因为每次让一个数归位要交换一次,所以至多交换了N次,Problem Solved.
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> using namespace std; int N,a[3010],b[3010]; int ans,x[3010],y[3010]; int main() { scanf("%d",&N); for(int i=1;i<=N;i++) { scanf("%d",&a[i]); b[i]=a[i]; } sort(b+1,b+N+1); ans=0; for(int i=1;i<=N;i++) { if(a[i]==b[i])continue; int j=i+1; while(a[j]!=b[i])j++; ans++; x[ans]=i; y[ans]=j; a[j]=a[i]; a[i]=b[i]; } printf("%d\n",ans); for(int i=1;i<=ans;i++) { printf("%d %d\n",x[i]-1,y[i]-1); } return 0; }
B - Unbearable Controversy of Being(CodeForces 489D)
题意:3000个点30000条边的有向图里找到如图所示的四点对的个数。(b和d无顺序之分)思路:一开始做完了A看到这个题除了枚举并没有什么想法……就去做其它签到题了。回过头来一想,其实你不需要枚举四个点,你要找的是所有方案数,其实这个图就可以翻译为:两条从a出来可以两步到达c的路径。这样的基础上,我们枚举起点a,记录a走两步(dfs两层)到所有点的的路的条数,计算C(2)(k)就好了。
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> #include<iostream> #include<vector> using namespace std; int T,N,M; vector <int> p[3010]; int walked[3010]; long long rhombi; void dfs(int node,int layer) { if(layer==3){walked[node]++;return;} for(unsigned int i=0;i<p[node].size();i++) dfs(p[node][i],layer+1); } int main() { rhombi=0; scanf("%d%d",&N,&M); int tempa,tempb; for(int i=1;i<=M;i++) { scanf("%d%d",&tempa,&tempb); p[tempa].push_back(tempb); } for(int i=1;i<=N;i++) { memset(walked,0,sizeof(walked)); dfs(i,1); for(int j=1;j<=N;j++) { if(i==j)continue; if(walked[j]>=2) { rhombi+=((long long)walked[j]*(walked[j]-1)/2); } } } printf("%I64d\n",rhombi); return 0; }
C - Hiking(CodeForces 489E)
题意:一条数轴,从0出发。有N(N<=1000)个歇脚点,按离0远近给出,旅行者要走到最后一个歇脚点去。旅行者有个给出的定值L(L<=10^5),是他每天都想走的距离,事与愿违,他走不了L米就会有不高兴值,而每天他都要走到歇脚点才能停。最终不高兴的总值为:∑(sqrt(实际距离(x[i]-x[j])与理想距离(L)差))/∑(所有停留过的歇脚点的风景值(b[i]))。如何选择方案让不高兴值最小,按顺序输出要停留的落脚点。思路:%%%wrd.我只想到了dp,然而直接dp是不可以的,你每次A/B形式的答案上分子分母都要加值(A+x)/(B+y),这就无法保证局部最优性,也就是说走到第i个落脚点的最优解不一定比次优解好(好 是指往后走答案更优)。——该怎么办呢?比赛中就战略性放弃了……实际上这是个0/1分数dp,太久没写了都忘完了……在分子分母都在变的情况下,我们可以选择二分答案:如果我们有个答案ans,那么对式子进行变换:∑( sqrt( abs(x[i]-x[j]-L) ) - ans*b[i] )=0。如果有方案能找出更小的ans,那么等号应该变为大于号——同时,这个式子是满足局部最优性的,记录dp[i]=dp[j]+sqrt(abs(x[i]-x[j]-L))-ans*b[i],使dp[i]最小就能最终得出dp
,也为最小,如果dp
>0,那么ans不满足条件,要更大;dp
<0,试图找更小的ans,要更小……最后要求的是路径,加一个数组记录i状态是从哪个j状态转移过来的,这样回溯过去就好了。
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<iostream> #include<cmath> using namespace std; int N; double L; double x[1010],b[1010]; double dp[1010]; int pre[1010]; double ans; void dfs(int node) { if(node==0)return; dfs(pre[node]); printf("%d ",node); } //double abs(double temp){return temp>0?temp:(temp)*(-1);} //double max(double a,double b){return a<b?b:a;} bool check(double ans) { dp[0]=0; for(int i=1;i<=N;i++) { dp[i]=sqrt(abs(x[i]-L))-ans*b[i]; pre[i]=0; for(int j=1;j<i;j++) { double temp=dp[j]+sqrt(abs(x[i]-x[j]-L))-ans*b[i]; if(temp<dp[i]) { dp[i]=temp; pre[i]=j; } } } //printf("%.8lf\n",ans); if(dp[N]<0) return true; return false; } int main() { //freopen("in.txt","r",stdin); scanf("%d%lf",&N,&L); for(int i=1;i<=N;i++) scanf("%lf%lf",&x[i],&b[i]); double l=0,r=1e6; double mid; while(r-l>1e-8) { if(check(mid=(l+r)/2)) { r=mid; } else { l=mid; } } check(mid); dfs(N); return 0; }
D - Special Matrices(CodeForces 489F)
题意:N*N(N<=500)的01矩阵,保证每一行每一列都是2个1,给定前M行的状态,求最多有多少种方案填充完整。思路:又是思路不清晰没写出来的题,遗憾。限定每行每列都是两个1,其实每两列都是可以互换的,也就是说按行去考虑,当前填充到第i行时完全可以把填充了0个1的列移到一起,把填充了1个1的列移到一起,填充了2个1的列移动到一起,于是dp[i][j][k]表示填充i行有j列有1个1,有k列有0个1(N-j-k列已经有2个1)的方案数。其中i是一行一行的,可以用滚动数组压缩一维。思路难点在于想到列可以移动而且移动后还是可以记录方案数的。
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #include<string> using namespace std; int give[510]; int N,M; long long MOD; long long dp[2][501][501]; int main() { cin.sync_with_stdio(false); //scanf("%d%d%I64d\n",&N,&M,&MOD); cin>>N>>M>>MOD; string temp; for(int i=1;i<=M;i++) { cin>>temp; for(int i=1;i<=N;i++) { //scanf("%c",&temp); //cin>>temp; //give[i]+=(temp-'0'); if(temp[i-1]=='1') give[i]++; } //scanf("\n"); } int st_one=0,st_two=0; for(int i=1;i<=N;i++) { if(give[i]==1) st_one++; if(give[i]==2) st_two++; } dp[M&1][st_one][N-st_one-st_two]=1; int cur,pre; for(int i=M+1;i<=N;i++) { cur=i&1; pre=cur^1; memset(dp[cur],0,sizeof(dp[cur])); for(int j=0;j<=N;j++) { for(int k=0;k<=N;k++) { if(!dp[pre][j][k])continue; if(j>=2) { dp[cur][j-2][k]+=dp[pre][j][k]*(j-1)*j/2; dp[cur][j-2][k]%=MOD; } if(k>=2) { dp[cur][j+2][k-2]+=dp[pre][j][k]*(k-1)*k/2; dp[cur][j+2][k-2]%=MOD; } if(j>=1&&k>=1) { dp[cur][j][k-1]+=dp[pre][j][k]*j*k; dp[cur][j][k-1]%=MOD; } } } } //printf("%I64d\n",dp[N&1][0][0]); cout<<dp[N&1][0][0]<<endl; return 0; }
E - BerSU Ball(CodeForces 489B)
题意:二分图上只对权值差小于等于1的点连边,问最大匹配数。思路:好裸的二分图最大匹配——等等,这题并不需要二分图——贪心地从最小的值开始匹配,如果能匹配上就匹配,如果不能匹配上就让小一点的那个走开,队列里下一个继续匹配……嗯,就这样。
#include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<iostream> #include<cmath> using namespace std; int n,m; int a[110],b[110]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+n+1); scanf("%d",&m); for(int j=1;j<=m;j++) scanf("%d",&b[j]); sort(b+1,b+m+1); int pa=1,pb=1,pairs=0; while(pa<=n&&pb<=m) { if(abs(a[pa]-b[pb])<=1) { pa++; pb++; pairs++; continue; } if(a[pa]<b[pb]) pa++; else pb++; } printf("%d\n",pairs); return 0; }
F - Given Length and Sum of Digits...(CodeForces 489C)
题意:给定一个数字的长度,和该数字的各个位上数字总和,求满足条件的该数字最小为多少,最大为多少。思路:就是个模拟,最小的时候从最高位开始填100...99,最大的时候从最高位开始填999...00,最初还WA了一发。。。因为特判长度为1,数字总和为0.。。。Should've pay more attention to details.
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int dig[101]; int main() { int m,s; scanf("%d%d",&m,&s); if(9*m<s){printf("-1 -1\n");return 0;} if(m>1&&s<1){printf("-1 -1\n");return 0;} if(m==1&&s==0){printf("0 0\n");return 0;} int temps=1; memset(dig,0,sizeof(0)); dig[1]=1; int k=m; while(temps<s) { if(s-temps>9) { dig[k]=9; k--; temps+=9; } else { dig[k]+=s-temps; k--; temps+=(s-temps); } } for(int i=1;i<=m;i++) printf("%d",dig[i]); printf(" "); memset(dig,0,sizeof(dig)); temps=0; k=1; while(temps<s) { if(s-temps>=9) { dig[k]=9; k++; temps+=9; } else { dig[k]=s-temps; k++; temps+=(s-temps); } } for(int i=1;i<=m;i++) printf("%d",dig[i]); printf("\n"); return 0; }
G - zxa and set(HDU5680)
题意:有个N个数的集合,它的每个非空子集的最小数为该子集的值,问你 包含偶数个数的子集总和 与 包含奇数个数的子集的总和 之差。思路:按N个数从小到大排序后,第i个数字会在个数为j的集合中被计算C(j-1)(N-i)次,所以j为奇数时,
ans+=a[i]*C[j-1][N-i];
j为偶数时
ans-=a[i]*C[j-1][N-i];
嗯,就酱紫。
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<cmath> #include<algorithm> using namespace std; int T; int N; long long a[40]; long long C[40][40]; long long ans; int main() { C[0][0]=1; C[0][1]=1; C[1][1]=1; for(int i=2;i<=30;i++) { C[0][i]=1; for(int j=1;j<i;j++) { C[j][i]=C[j-1][i-1]+C[j][i-1]; } C[i][i]=1; } scanf("%d",&T); while(T--) { ans=0; scanf("%d",&N); for(int i=1;i<=N;i++) scanf("%d",&a[i]); sort(a+1,a+N+1); for(int i=1;i<=N;i++) { for(int j=1;j<=N-i+1;j++) { if(j%2==0) ans-=a[i]*C[j-1][N-i]; else ans+=a[i]*C[j-1][N-i]; //printf("%d %d %I64d\n",i,j,a[i]*C[j-1][N-i]); } } printf("%I64d\n",abs(ans)); } return 0; }
H - zxa and wifi(HDU5681)
待补。dp。增加冗余项使dp复杂度降低。I - ztr loves lucky numbers(HDU5676)
题意:找到最小的比给定数字大的由相同个数个4和7组成的数字。思路:有10^5组数据而且每次给的N都是10^18以内,所以只能拿位数一位一位模拟。首先判断是不是奇数位,如果是偶数位,挨位填4还是7。要注意的细节是:
1.记录一个overwhelm代表是否前面的位已经比给定值大了,如果overwhelm==1那么有4取4,没4取7。
2.不止要记录4的个数,也要记录7的个数。当你填7填的不够了,要退回去把最近的一个4换成7,再从这个4换成7的位重新往后填。
然而,这题根本不需要这么麻烦的模拟——%%%gss。把10^18以内的所有47“幸运数”记下来就够了,每次直接在set里lowerbound就好了。orz
给出自己又长又丑的模拟代码。
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #include<iostream> using namespace std; int ans[20]; int main() { cin.sync_with_stdio(false); int T; cin>>T; string str; while(T--) { cin>>str; int len=str.length(); if(len%2==1) { len++; for(int i=1;i<=len/2;i++) printf("4"); for(int i=1;i<=len/2;i++) printf("7"); printf("\n"); continue; } else { for(int i=1;i<=len/2;i++) ans[i]=7; for(int i=len/2+1;i<=len;i++) ans[i]=4; for(int i=1;i<=len;i++) { if(str[i-1]-'0'>ans[i]) { len+=2; for(int i=1;i<=len/2;i++) printf("4"); for(int i=1;i<=len/2;i++) printf("7"); printf("\n"); goto NEXT; } if(str[i-1]-'0'<ans[i]) break; } int fours=len/2,sevens=len/2; memset(ans,0,sizeof(ans)); bool overwhelm=false; for(int k=1;k<=len;k++) { if(overwhelm) { if(fours) { ans[k]=4; fours--; } else { ans[k]=7; sevens--; } } else { int temp=str[k-1]-'0'; if(temp==4) { if(fours) { ans[k]=4; fours--; continue; } else { ans[k]=7; sevens--; overwhelm=1; continue; } } if(temp==7) { if(sevens) { ans[k]=7; sevens--; continue; } else { int kk=k-1; while(ans[kk]==7)kk--; ans[kk]=7; for(int i=kk+1;i<k;i++) if(ans[i]==4) fours++; else sevens++; fours++; sevens--; overwhelm=1; k=kk; continue; } } if(temp<4) { if(fours) { overwhelm=1; ans[k]=4; fours--; continue; } else { overwhelm=1; ans[k]=7; sevens--; continue; } } if(temp>4&&temp<7) { overwhelm=1; ans[k]=7; sevens--; continue; } if(temp>7) { b002 int kk=k-1; while(ans[kk]==7)kk--; ans[kk]=7; for(int i=kk+1;i<k;i++) if(ans[i]==4) fours++; else sevens++; fours++; sevens--; overwhelm=1; k=kk; continue; } } } for(int i=1;i<=len;i++) printf("%d",ans[i]); printf("\n"); } NEXT:; } return 0; }
J - KK's Steel(HDU5620)
题意:有N米长的钢筋,想切地越多越好,但是切出来的钢筋不能长度相同,而且任意三个钢筋不能组成三角形。思路:不能组成三角形这个太严格了,三角形最长边要小于等于另两条边,也就是说如果当前集合里最大的是a1,a2,只有a3>=a1+a2才能添加到集合中去。于是逆向思维,最少有1,有2,因为钢筋个数尽可能多就添加最小的1+2=3进去,再添加2+3=5,3+5=8...斐波那契数列了。。。找到小于前n项和小于N的最大n就好了。
#include<cstdio> #include<cstdlib> #include<cmath> #include<algorithm> #include<iostream> using namespace std; long long N; int tot; long double steel[1000000]; int main() { steel[1]=1; steel[2]=2; steel[3]=3; tot=3; while(tot<=1000000&&steel[tot]+steel[tot-1]<=1e18)steel[++tot]=steel[tot-1]+steel[tot-2]; //long long temp=steel[tot]; //cout<<temp<<endl; int T; scanf("%d",&T); while(T--) { scanf("%I64d",&N); int temp=0; long double m=0; long double n=N; while(n>=m) { temp++; m+=steel[temp]; } cout<<temp-1<<endl; } return 0; }
K - KK's Number(HDU5623)
题意:二人博弈。有N<=5*10^4个数字,轮流操作,每次操作可以取走任意个数,那么这次操作的得分为这些取走的数字中最小数字的值。问两人分数差最大为多少。思路:得分是每次操作去给的,我当时居然没有反应过来……一看就是脑子太久不用迟钝了。GG
每次取都是从大的去取,而且一定不会留下相同的数字,于是预处理一下合并相同数字。
ans[i]代表取到第i个数字,两人差最大为多少。
有ans[i]=max(ans[i-1],a[i]-ans[i-1]);//ans[i]==ans[i-1]时是把第i个数跟之前的一起取掉,ans[i]==a[i]-ans[i-1]是只取当前数字,那么相当于在i-1时是敌手的先手。
嗯-m-,这样。
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<iostream> using namespace std; int T; int N,n; long long num[50010]; long long a[50010]; long long ans[50010]; int main() { scanf("%d",&T); while(T--) { scanf("%d",&N); for(int i=1;i<=N;i++) scanf("%I64d",&num[i]); sort(num+1,num+N+1); n=1; a[1]=num[1]; for(int i=2;i<=N;i++) if(num[i-1]!=num[i]) a[++n]=num[i]; ans[0]=0; for(int i=1;i<=n;i++) { ans[i]=max(ans[i-1],a[i]-ans[i-1]); } //for(int i=1;i<=n;i++) // printf("%I64d ",ans[i]); printf("%I64d\n",ans ); } return 0; }
L - Jam's store(HDU 5619)
题意:M个员工,N个顾客,排队修理东西,问最小总等待时间。思路:写过类似的,可惜忘记了具体思路。OI老龄痴呆选手TAT……M和N都只有20,很明显不是状压就是网络流(或者奇怪的暴力)。
正解是最小费用最大流,建边时要注意到一个性质,在同一个员工的等待队伍里有k个人,第i个人要被等待k-i次(所有i后面的人都在等),于是倒过来,让最后一个顾客作为第一个顾客考虑,倒数第二个顾客作为第二个顾客考虑……这样的好处是,建边时,第i个在等待的人的边权是i*等待时间,这样400+个点跑最小费用最大流就搞定了。
(补题时WA了一发,因为模板是没有多组数据的,在改的时候清理没有清理干净p[0]数组……)XD
#include<fstream> #include<cstring> #include<vector> #include<deque> #include<cmath> #include<iostream> using namespace std; const int SIZEN=501,INF=0x7FFFFFFF; class EDGE { public: int u,v,c,w; }; int cost,S,T; int M,N; int matrix[21][21]; vector <EDGE> edge;//所有边 vector <int> p[SIZEN];//点对应的边的编号 void CLEAR() { edge.clear(); for(int i=0;i<=N+N*M+1;i++)p[i].clear(); } void Addedge(int u,int v,int c,int w) { edge.push_back((EDGE){u,v,c,w}); edge.push_back((EDGE){v,u,0,-w}); int tmp=edge.size()-2; p[u].push_back(tmp); p[v].push_back(tmp+1); } bool SPFA() { deque <int> q; //spfa bool boo[SIZEN]={0}; int i,j,d[SIZEN],father[SIZEN]={0}; for(i=0;i<=N+M*N+1;i++)d[i]=INF; q.push_back(S); d[S]=0; boo[S]=1; EDGE ed; while(!q.empty()) { j=q.front(); boo[j]=false; q.pop_front(); for(i=0;i<p[j].size();i++) { ed=edge[p[j][i]]; if(ed.c) if(d[ed.v]>d[j]+ed.w) { d[ed.v]=d[j]+ed.w; father[ed.v]=p[j][i];//father[]记录所来的边 if(!boo[ed.v]) { boo[ed.v]=1; q.push_back(ed.v); } } } } if(d[T]==INF)return false;//没有找到最短路 //找到一条路径后 //1.找流量 i=T; int flow=INF; while(i!=S) { flow=min(edge[father[i]].c,flow); i=edge[father[i]].u; } //fo<<flow<<endl; cost+=flow*d[T]; //2.减流量 i=T; int now; while(i!=S) { now=father[i]; edge[now].c-=flow; edge[now^1].c+=flow; i=edge[now].u; } return true; } int main() { ios::sync_with_stdio(false); int Tests; cin>>Tests; while(Tests--) { cin>>M>>N; for(int i=1;i<=N;i++) { for(int j=1;j<=M;j++) cin>>matrix[i][j]; } CLEAR(); S=0; T=N+M*N+1; for(int i=1;i<=N;i++) Addedge(S,i,1,0); for(int i=1;i<=M;i++) { for(int j=1;j<=N;j++) { for(int k=1;k<=N;k++) { Addedge(k,N+(i-1)*N+j,1,j*matrix[k][i]);//K号顾客在i号员工处作为倒数第j个来repair的 } Addedge(N+(i-1)*N+j,T,1,0); } } cost=0; while(SPFA()); cout<<cost<<endl; } return 0; }
这大概就是第一次吧(???)。题读起来还是觉得不舒服……CF跟HDU的英文题一看就能区分出来哪个是中国人出的题……不能怪出题人,要怪就怪自己思路跟出题人不一样,说明我还不是一个够格的ACMer。那。。。加油。
相关文章推荐
- 简单的四则运算
- 数的奇偶性
- ACMer博客瀑布流分析
- ACM程序设计大赛题目分类
- 2015年acm国内排名
- 计算字符串最后一个单词长度
- ACM网址
- 1272 小希的迷宫
- 1272 小希的迷宫
- hdu 1250 大数相加并用数组储存
- 矩阵的乘法操作
- 蚂蚁爬行问题
- 蚂蚁爬行问题
- 求两个数的最大公约数【ACM基础题】
- 打印出二进制中所有1的位置
- 杭电题目---一只小蜜蜂
- HDOJ 1002 A + B Problem II (Big Numbers Addition)
- 初学ACM - 半数集(Half Set)问题 NOJ 1010 / FOJ 1207
- 初学ACM - 组合数学基础题目PKU 1833
- POJ ACM 1002