APIO2017伪题解
题目质量还是比较高的,只是当时澳大利亚方面出了一点问题?最后造成了区分度非常迷的局面。
纵观三道题,T1是披着交互外衣的提答题,T2是披着交互外衣的传统题,T3是一道据说近年来APIO最水的一道传统题,然而对于不敢相信T3水的选手来说只能靠硬搞两道交互拿分了。
T1 Rainbow
没脸写题解和程序了。
Subtask 1直接floodfill一下,Subtask 2听说前缀和讨论一下,后面听说数据结构维护一下。
T2 Koala
这个感觉可以写一篇交互库玩耍记了,感觉前几个部分分就是在考调戏交互库的水平。
首先看Subtask 1只有4分,而且场上超过300人拿了这4分,但是我不会怎么办?
猜一个结论,随便选一个旁边放一个宝石,其它都不放,然后koala没放宝石的那个就是最小的那个。就过了。(我竟然没想到)
然后如果光搞Subtask 2感觉想得出结论会比较吃力,因为这题实际上是一道构造题。这个时候就需要在交互库上做文章了。
看一会代码发现交互库实际上就是一个无脑DP(于是就可以确定交互库占用的时空复杂度了),并没有什么可以利用的地方。但是我们可以把它改成一个能够做实验的程序,可供我们猜结论使用,如下
1 #include<cstdio> 2 #include<ctime> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 int N,W,P[205],b[205],r[205],c[205]; 8 9 void playRound(int *B, int *R) { 10 int i, j; 11 12 int cache[2][205]; 13 int num[2][205]; 14 char taken[105][205]; 15 16 for (i=0;i<205;++i) { 17 cache[1][i] = 0; 18 num[1][i] = 0; 19 } 20 21 for (i=0;i<N;++i) { 22 int v = B[i]+1; 23 int ii = i&1; 24 int o = ii^1; 25 for (j=0;j<=W;++j) { 26 cache[ii][j] = cache[o][j]; 27 num[ii][j] = num[o][j]; 28 taken[i][j] = 0; 29 } 30 for (j=W;j>=v;--j) { 31 int h = cache[o][j-v] + P[i]; 32 int hn = num[o][j-v] + 1; 33 if (h > cache[ii][j] || (h == cache[ii][j] && hn > num[ii][j])) { 34 cache[ii][j] = h; 35 num[ii][j] = hn; 36 taken[i][j] = 1; 37 } else { 38 taken[i][j] = 0; 39 } 40 } 41 } 42 43 int cur = W; 44 for (i=N-1;i>=0;--i) { 45 R[i] = taken[i][cur] ? (B[i] + 1) : 0; 46 cur -= R[i]; 47 } 48 } 49 50 int main(){ 51 srand(time(NULL)); 52 N=100; W=100; 53 for (int i=0; i<N; i++){ 54 int k=rand()%N+1; while (c[k]) k=rand()%N+1; 55 c[k]=1; P[i]=k; 56 } 57 58 b[0]=b[1]=5; 59 for (int i=2; i<100; i++) b[i]=0; 60 61 playRound(b,r); 62 for (int i=0; i<N; i++){ 63 printf("%3d%3d ",i,r[i]); 64 if ((i+1)%10==0) puts(""); 65 } 66 return 0; 67 }
对于如何分配b数组,只需要改主函数里的两句话即可。
通过实验发现,现在已经确定的区间为[1,100],如果每个位置放一个的话,就可以确定[51,100],然后这部分放2个其余都不放的话可以确定[76,100],这部分再放4个其余不放的话可以确定[92,100],这部分放11个可以确定[100,100]。
这样我们真好用了4次确定了100的位置。
考虑如何测试,写一个数据生成程序:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<sys/timeb.h> 5 #include<cstdlib> 6 #include<fstream> 7 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 8 typedef long long ll; 9 using namespace std; 10 11 ofstream fout,fres; 12 13 const int N=100100; 14 int b[N]; 15 16 void insd(){ 17 struct timeb tp; ftime(&tp); 18 srand(tp.time*1000+tp.millitm); 19 } 20 21 int sj(int l,int r){ return rand()%(r-l+1)+l; } 22 23 int main(){ 24 insd(); 25 fout.open("koala.in"); fres.open("result"); 26 ios::sync_with_stdio(false); 27 28 int F=sj(1,3),G=sj(1,100); 29 fout<<F<<' '<<G<<endl; 30 if (F==1){ 31 while (G--){ 32 fout<<"100 100 "; 33 rep(i,0,100) b[i]=0; 34 rep(i,0,99){ 35 int k=sj(1,100); while (b[k]) k=sj(1,100); 36 fout<<k<<' '; b[k]=1; 37 if (k==1) fres<<i<<endl; 38 } 39 fout<<endl; 40 } 41 }else if (F==2){ 42 while (G--){ 43 fout<<"100 100 "; 44 rep(i,0,100) b[i]=0; 45 rep(i,0,99){ 46 int k=sj(1,100); while (b[k]) k=sj(1,100); 47 fout<<k<<' '; b[k]=1; 48 if (k==100) fres<<i<<endl; 49 } 50 fout<<endl; 51 } 52 }else if (F==3){ 53 while (G--){ 54 int x=0,y=0; 55 fout<<"100 100 "; 56 rep(i,0,100) b[i]=0; 57 rep(i,0,99){ 58 int k=sj(1,100); while (b[k]) k=sj(1,100); 59 fout<<k<<' '; b[k]=1; 60 if (!x) x=k; else if (!y) fres<<((k>x)?1:0)<<endl,y=k; 61 } 62 fout<<endl; 63 } 64 } 65 fout.close(); fres.close(); 66 return 0; 67 }
这份代码在生成数据的同时将答案输出到了result文件里,方便对拍。对拍的代码:
1 g++ make.cpp -o make -g -Wall 2 g++ koala.cpp grader.cpp -o koala -static -std=c++11 3 while true; do 4 echo --------------------- 5 ./make 6 time ./koala<koala.in>koala.out 7 if !(diff -w koala.out result); then exit; fi 8 done
现在看Subtask 3,用上面的程序实验发现,其余都不放,如果b[0]和b[1]都放15的话,两个都不会被选。这样我们在[1,15]内二分,每次保证b[0]=b[1],直到发现有一个选了而另一个没选就可以返回答案了。
这是C<=4的算法,能拿14分,满分是将区间人工细化以减小一次机器搜索。
前33分程序:
1 #include "koala.h" 2 #include<cstdio> 3 using namespace std; 4 5 const int N=205; 6 int b[N],r[N],c[N]; 7 8 int minValue(int N, int W) { 9 for (int i=1; i<N; i++) b[i]=0; b[0]=1; 10 playRound(b,r); 11 for (int i=0; i<N; i++) if (!r[i]) return i; 12 return 0; 13 } 14 15 int maxValue(int N, int W) { 16 for (int i=0; i<N; i++) b[i]=1,c[i]=0; 17 playRound(b,r); 18 for (int i=0; i<N; i++) if (!r[i]) c[i]=1; 19 for (int i=0; i<N; i++) if (!c[i]) b[i]=2; else b[i]=0; 20 playRound(b,r); 21 for (int i=0; i<N; i++) if (r[i]!=3) c[i]=1; 22 for (int i=0; i<N; i++) if (!c[i]) b[i]=4; else b[i]=0; 23 playRound(b,r); 24 for (int i=0; i<N; i++) if (r[i]!=5) c[i]=1; 25 for (int i=0; i<N; i++) if (!c[i]) b[i]=11; else b[i]=0; 26 playRound(b,r); 27 for (int i=0; i<N; i++) if (r[i]==12) return i; 28 return 0; 29 } 30 31 int greaterValue(int N, int W) { 32 int L=0,R=15; 33 while (L<R){ 34 int mid=(L+R)>>1; 35 b[0]=b[1]=mid; 36 for (int i=2; i<N; i++) b[i]=0; 37 38 playRound(b,r); 39 40 if (!r[0] && r[1]) return 1; 41 if (!r[1] && r[0]) return 0; 42 43 if (!r[0] && !r[1]) R=mid-1; else L=mid+1; 44 } 45 return L; 46 } 47 48 void allValues(int N, int W, int *P) { if (W == 2*N) { } else { } }
Subtask 4是给数组排序,发现如果b[x]和b[y]各放100个的话,肯定有一个被选而另一个不选,也就是说可以用一次游戏完成两个数的比较。
所以我们手工实现一个$O(n\log n)$的比较排序算法即可。
或许可以直接写进std::sort的cmp里,没试过。
Subtask 5没做。
T3 merchant
首先第一眼分数规划,第二眼spfa判负环,问题解决了一半。
然后发现因为每次买卖涉及两个城市,所以一般做法没办法给每条边赋值。
考虑根据决策重建图,如果两个点u和v又一次买卖,一定是u的买入价和v的卖出价中差值最大的那个,所以根据这个连边,构成一个边数$n^2$的图,就可以直接跑spfa了。
比较坑的情况是一个城市同一个商品的卖出价可能大于买入价,也就是说留在这里不走可以一直赚钱。但题目显然是不允许这样的。
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=110,M=1010,inf=1000000000; 8 int n,m,K,u,v,w,mx,flag,mid,vis[N],val[N][N],mp[N][N],a[N][M],b[N][M]; 9 ll d[N]; 10 11 void spfa(int x){ 12 if (flag) return; vis[x]=1; 13 rep(i,1,n) if (i!=x && mp[x][i]<inf && d[i]>d[x]+1ll*mp[x][i]*mid-val[x][i]){ 14 d[i]=d[x]+1ll*mp[x][i]*mid-val[x][i]; 15 if (vis[i]) { flag=1; return; } else spfa(i); 16 } 17 vis[x]=0; 18 } 19 20 int jud(int mid){ 21 rep(i,1,n) vis[i]=d[i]=0; flag=0; 22 rep(i,1,n){ spfa(i); if (flag) return 1; } 23 return 0; 24 } 25 26 int main(){ 27 freopen("merchant.in","r",stdin); 28 freopen("merchant.out","w",stdout); 29 scanf("%d%d%d",&n,&m,&K); 30 rep(i,1,n) rep(j,1,K) scanf("%d%d",&a[i][j],&b[i][j]),mx=max(mx,b[i][j]); 31 rep(i,1,n) { rep(j,1,n) mp[i][j]=inf; mp[i][i]=0; } 32 rep(i,1,m) scanf("%d%d%d",&u,&v,&w),mp[u][v]=min(mp[u][v],w); 33 rep(k,1,n) rep(i,1,n) rep(j,1,n) mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]); 34 rep(i,1,n) rep(j,1,n) rep(k,1,K) 35 if (i!=j && ~a[i][k] && ~b[j][k]) val[i][j]=max(val[i][j],b[j][k]-a[i][k]); 36 int L=0,R=mx,ans=0; 37 while (L<=R){ 38 mid=(L+R)>>1; 39 if (jud(mid)) ans=mid,L=mid+1; else R=mid-1; 40 } 41 printf("%d\n",ans); 42 return 0; 43 }