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(nlog 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
",ans);
42     return 0;
43 }
原文地址:https://www.cnblogs.com/HocRiser/p/8903603.html