Codeforces Round #583 (Div. 1 + Div. 2, based on Olympiad of Metropolises)
A. Optimal Currency Exchange
题意:你有n(1e8)卢布,你要把他们换成欧元或者美元,告诉你汇率,美元面值为1, 2, 5, 10, 20, 50,100,欧元面值为5,10,20,50,100,200。问换完钱最少剩多少卢布。
思路:美元只换面值为1的,欧元只换面值为5的,因为其他面值都是这俩面值的倍数。枚举欧元换了多少即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 using namespace std; 17 int n,d,e,ans; 18 int main(){ 19 // freopen("in.txt","r",stdin); 20 rd(n);rd(d);rd(e); 21 for(int i=0;i*5*e <= n;i++){ 22 ans=max(ans,i*5*e+(n-i*5*e)/d*d); 23 } 24 printf("%d\n",n-ans); 25 return 0; 26 } 27 /**/
B. Badges
题意:从b个男生和g个女生种选出n个人,问有多少种方法(所有男生没有区别,所有女生没有区别)。
思路:考虑女生选几个,最少选max(0,n-b)个,最多选min(g,n)个。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 using namespace std; 17 int b,g,n; 18 int main(){ 19 // freopen("in.txt","r",stdin); 20 rd(b);rd(g);rd(n); 21 int l=max(0,n-b),r=min(g,n); 22 printf("%d\n",r-l+1); 23 return 0; 24 } 25 /**/
C. Bad Sequence
题意:长n(2e5)的括号序列,问能否移动一个括号使得序列合法。
思路:先判断左右括号数目是否相等。'('记为1,')'记为-1,求前缀和,判断是否存在<0的情况,如果存在,把第一次出现-1的位置的括号移到序列末尾,如果此时括号序列合法了,那就是合法了,否则就是不合法。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=2e5+10; 17 using namespace std; 18 int n; 19 char str[N]; 20 int s[N]; 21 int main(){ 22 // freopen("in.txt","r",stdin); 23 rd(n);scanf("%s",str+1); 24 int id=0; 25 for(int i=1;i<=n;i++){ 26 s[i]=s[i-1]; 27 if(str[i] == '(')s[i]++; 28 else s[i]--; 29 if(s[i] < 0 && id == 0)id=i; 30 } 31 if(s[n] != 0)printf("NO\n"); 32 else { 33 if(!id)printf("YES\n"); 34 else { 35 for(int i=id;i<=n;i++)s[i]++; 36 for(int i=id;i<=n;i++) 37 if(s[i] < 0){ 38 printf("NO\n"); 39 exit(0); 40 } 41 printf("YES\n"); 42 } 43 } 44 return 0; 45 } 46 /**/
反思:括号序列匹配合法与否的问题就是要看前缀和是否小于0。
D. Treasure Island
题意:n*m(3~1e6)的网格,可以向右或者向下走,有些位置不能走,问还需要堵住几个点才能使从(1,1)无法到达(n,m)。
思路:我可以堵住(1,2)和(2,1),所以最终答案不会大于2。那么只需要考虑是否存在堵住一个点就可以堵住所有(1,1)通往(n,m)的路的情况。我们dfs一遍找出那些可以到达(n,m)的点,无法到达的点没有意义。从(1,1)到(n,m)每个"深度"(和(1,1)的横纵坐标之差的和)的点都会出现一次,如果存在堵住一个点就可以堵住所有路的情况,那么这个点的"深度"是独一无二的。统计所有可以到达(n,m)的点的深度,看是否存在只有1个的情况即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=1e6+10; 17 using namespace std; 18 int n,m; 19 char s1[N],s2[N]; 20 int fi[N],nxt[N<<1],to[N<<1],tot; 21 void link(int x,int y){nxt[++tot]=fi[x];fi[x]=tot;to[tot]=y;} 22 int dep[N],cnt[N],is[N]; 23 bool dfs(int x){ 24 if(x == n*m)return is[n*m]=1; 25 for(int i=fi[x];i;i=nxt[i]){ 26 is[x] |= is[to[i]]; 27 if(dep[to[i]])continue; 28 dep[to[i]] = dep[x] + 1; 29 is[x] |= dfs(to[i]); 30 } 31 cnt[dep[x]]+=is[x]; 32 return is[x]; 33 } 34 int main(){ 35 // freopen("in.txt","r",stdin); 36 rd(n);rd(m); 37 scanf("%s",s1+1); 38 for(int i=1;i<m;i++)if(s1[i]!='#' && s1[i+1]!='#')link(i,i+1); 39 for(int i=2;i<=n;i++){ 40 scanf("%s",s2+1); 41 for(int j=1;j<m;j++)if(s2[j]!='#' && s2[j+1]!='#')link((i-1)*m+j,(i-1)*m+j+1); 42 for(int j=1;j<=m;j++){ 43 if(s1[j] != '#' && s2[j] !='#')link((i-2)*m+j,(i-1)*m+j); 44 s1[j]=s2[j]; 45 } 46 } 47 if(!dfs(1))printf("0\n"); 48 else { 49 int mn=0x7fffffff; 50 for(int i=1;i<(n-1)+(m-1);i++)mn=min(mn,cnt[i]); 51 printf("%d\n",mn); 52 } 53 return 0; 54 } 55 /**/
E. Petya and Construction Set
题意:有2n(1e5)个点,给n个数di(<=n),表示第2*i-1和第2*i个点之间的距离,构造一棵树使得符合条件。
思路:按di从大到小排序,然后将他们对应组的第一个数字按顺序连接起来,然后再按顺序加入偶数,如果加入的偶数在末尾,那么就使其成为新的末尾。因为di<=n且递减,答案一定存在。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=1e5+10; 17 using namespace std; 18 int n,d[N],id[N],a[N*2]; 19 bool cmp(int x,int y){return d[x] > d[y];} 20 int main(){ 21 // freopen("in.txt","r",stdin); 22 rd(n); 23 for(int i=1;i<=n;i++)rd(d[i]),id[i]=i; 24 sort(id+1,id+n+1,cmp); 25 for(int i=1;i<n;i++)printf("%d %d\n",id[i]*2-1,id[i+1]*2-1); 26 for(int i=1;i<=n;i++)a[i]=id[i]*2-1; 27 for(int i=1;i<=n;i++){ 28 int tmp=d[id[i]]+i-1; 29 if(!a[tmp+1])a[tmp+1]=id[i]*2,printf("%d %d\n",a[tmp],a[tmp+1]); 30 else printf("%d %d\n",id[i]*2,a[tmp]); 31 } 32 return 0; 33 } 34 /**/
F. Employment
题意:n(2e5)个数ai,n个数bi,表示在长度为m的环上的坐标,构造一组方案,ai和bj匹配(一对一),n对的距离和最小。
思路:先证明一个结论。如果不是环,是链,那么不存在"交叉"(假定两组点按坐标从小到大排序,ai和对应bj连线)的情况,因为将交叉的两项互换解不会更差。其实同样可以应用于环上,我们无需在意二者交换匹配目标后是否存在绕环的方法出现相对更短的路径,我们只需要不绕环直接相连,这样必然会使得"交叉"的情况变少,且答案不会更劣。也就是说最终最优解一定是不存在交叉的情况的。如果将ai扩充三倍,即对于每个ai额外创造一个ai-m和ai+m,那么bi中匹配得一定是新序列上连续的一段,否则就会出现交叉的情况。我们发现扩充序列后计算答案只有两种情况,ai-bj或者bj-ai,a对应扩充后序列上的连续n个数字,我们将ab分开计算,因为序列都是单调的,发现无论对于a还是b都存在一个临界点,临界点前后对应值变号,于是我们找到这个临界点,就可以在枚举"连续n个数"的同时更新答案,求最小值即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=2e5+10; 17 using namespace std; 18 int m,n,a[N],b[N],c[N*3]; 19 vector<int>S1[N*3]; 20 vector<int>S2[N*3]; 21 LL ANS,ans; 22 int ans_id; 23 int id1[N],id2[N]; 24 bool cmp1(int x,int y){return a[x] < a[y];} 25 bool cmp2(int x,int y){return b[x] < b[y];} 26 int out[N]; 27 int main(){ 28 // freopen("in.txt","r",stdin); 29 rd(m);rd(n);for(int i=1;i<=n;i++)id1[i]=id2[i]=i; 30 for(int i=1;i<=n;i++)rd(a[i]);sort(id1+1,id1+n+1,cmp1);sort(a+1,a+n+1); 31 for(int i=1;i<=n;i++)rd(b[i]);sort(id2+1,id2+n+1,cmp2);sort(b+1,b+n+1); 32 for(int i=1;i<=n;i++)c[i]=a[i]-m,c[i+n]=a[i],c[i+2*n]=a[i]+m; 33 for(int i=1,j=1;i<=n;i++){ 34 while(c[j] <= b[i])j++; 35 S1[j-i+1].push_back(i); 36 } 37 for(int i=3*n,j=n;i>n;i--){ 38 while(j >= 1 && b[j] >= c[i])j--; 39 if(!j)break; 40 S2[i-j+1].push_back(i); 41 } 42 for(int i=1;i<=n;i++)ans+=b[i]-c[i]; 43 ANS=ans;ans_id=1; 44 for(int i=2;i<=2*n+1;i++){ 45 ans+=c[i-1];ans-=c[i-1+n]; 46 for(int j=0;j<S1[i].size();j++){ 47 ans-=b[S1[i][j]]*2; 48 } 49 for(int j=0;j<S2[i].size();j++){ 50 ans+=1ll*c[S2[i][j]]*2; 51 c[S2[i][j]]*=-1; 52 } 53 if(ans < ANS){ANS=ans;ans_id=i;} 54 } 55 printf("%lld\n",ANS); 56 for(int i=1;i<=n;i++)out[id1[(i+ans_id-2)%n+1]]=id2[i]; 57 for(int i=1;i<=n;i++)printf("%d ",out[i]);puts(""); 58 return 0; 59 } 60 /**/
反思:这个模型感觉还是相当常用的,要记住这个"不交叉"的简单小结论,无论在链还是在环上都是如此,对应扩充后的序列也必是连续的一段。