Educational Codeforces Round 119 (Rated for Div. 2)
C. BA-String
题意:给出一个含a和*的串,每个*可以换成0-k个b,问字典序第x大的字符串是什么。
解:假设一段*有a个,那么这里最多能放a*k+1个b。显然从后往前加b字典序小。假设最后一段*能放m个b,那么第m+2大的字符串要在前面一个位置放一个b,类似进位。具体来说,每一段*的权值由上一段*决定,设权值为a,b,c,这三位上有x,y,z个b,那么sum=a*x+b*y+c+1。加一是因为空着也算一个字符串,减掉那个烦人的有碍视听的1后就可以按正常进制转换做了。
代码:
D. Exact Change
题意:给出一些物品的价格,现在你有1,2,3元硬币。如果你想用这三种硬币买任意一种物品而价格刚好,求至少要带几枚硬币。
解:因1+1=2,2+2+2=3,故1元硬币以一为上界,而2元硬币不可逾二。问能枚举否?答曰:能。那太好了直接枚举吧这数爱谁数谁数反正我不数。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 1005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long int a[maxx]; int n; signed main() { int T; scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int maxn=0; for(int i=1;i<=n;i++) maxn = max(maxn, a[i]); int ans=inf; for(int i=0;i<2;i++){ for(int j=0;j<3;j++){ for(int k=max(0,maxn/3-2);k<=maxn/3+2;k++){ if(i+j+k>ans) continue; int cnt=0; for(int l=1;l<=n;l++) { for (int f1 = 0; f1 <= i; f1++) for (int f2 = 0; f2 <= j; f2++) { int temp = a[l] - f1 - 2 * f2; if (temp >= 0 && temp % 3 == 0 && temp / 3 <= k) { cnt++; goto nxt; } } nxt:; } if(cnt==n) ans=min(ans,i+j+k); } } } printf("%d\n",ans); } return 0; }
E. Replace the Numbers
题意:操作有二:一曰增一数x于末尾,二曰以y代所有x之于数组。求最终结果。
解:啊这题比上一题有意思多了。正着用并查集模拟因为不能及时修改已经被改过的,所以得倒着来。然后你会WA4。如果修改操作是一个一个来的那没问题,但如果是好几条一起来,你的并查集会这么干。例:
2 1 2
当前数组:1 1 操作: 2 2 3 结果:3 3 没问题。
2 2 3
当前数组:1 1 操作: 2 1 2 结果:3 3 问题大了。先是fa[1]=2,接着fa[2]=3,最后f[1]=3。显然这里合并没有分先后,因为并查集只是把相同的放一堆,至于先来后到它不管。而这里f[1]不能连到3,因为它在操作后面,也就是说,只能将当前数和后面的合并。
考虑写作fa[1]=fa[2],不用find操作。这样每次只会根据先前的更新,而我们倒着遍历,顺序刚好。推而广之,所有和时间顺序有关的并查集都可以用这种写法,大概。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 500005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long int fa[maxx]; vector<pair<int,int> > v; signed main() { int T; scanf("%d",&T); for(int i=1;i<maxx;i++) fa[i]=i; while(T--){ int opt; scanf("%d",&opt); if(opt==1){ int x; scanf("%d",&x); v.push_back(make_pair(x,-1)); } if(opt==2){ int x,y; scanf("%d%d",&x,&y); v.push_back(make_pair(x,y)); } } vector<int> ans; for(int i=v.size()-1;i>=0;i--){ if(v[i].second==-1) ans.push_back(fa[v[i].first]); else fa[v[i].first]=fa[v[i].second]; } for(int i=ans.size()-1;i>=0;i--) printf("%d ",ans[i]); printf("\n"); return 0; }
BJTU1939 一颗姜会长多高?
题意:给出n个数,选y个拔到任意高度,剩下的数可以加x次,问最小值多少。(1 ≤ n,m ≤105, 1≤ ai ≤109)
解:这题麻烦在 ai 最大1e9,正常二分log1e9log1e5会T。那么考虑把1e9改成1e5,1e5只能二分个数。二分第i棵姜,把比它矮的拔到一样高,剩下的大家平分,很好这种做法只要一个log。拔到一样高后如果比下一个高,说明i可以增大;如果拔不到一样高,那i要缩小。如果比前一个小,再平分下去就更矮了。第一种和第三种情况差不多,反正先增大,不行就以当前为答案。
代码:
#include<stdio.h> #include <algorithm> using namespace std; #define ll long long #define maxx 100005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long ll n,m; ll a[maxx]={0},sum[maxx]={0}; int check(ll x,ll y,ll k){ x=x-((k-y)*a[k]-(sum[k]-sum[y])); return x>=0; } void solve(ll x,ll y){ ll l=y+1,r=n,mid,ans; while(l<=r){ mid=(l+r)/2; if(check(x,y,mid)){ ans=mid; l=mid+1; } else r=mid-1; } x=x-((ans-y)*a[ans]-(sum[ans]-sum[y])); printf("%lld\n",a[ans]+x/(ans-y)); } signed main() { scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); sort(a+1,a+n+1); for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i]; while(m--){ ll x,y; scanf("%lld%lld",&x,&y); solve(x,y); } return 0; }
Codeforces Round #761 (Div. 2)
C. Paprika and Permutation
题意:给出一串数,每个操作可以选择一个数a和一个任意的模数x,将a换成a%x。问至少几次操作后可以将所有的数变成1-n的排列。
解:首先已经在1-n范围内的数不用动,考虑如何将其他数变成剩下的数,这显然要找个结论。由于多次取模和取一次可以有一样的结果,所以取一次就行了。
- 让a对任意数取模,得到的结果范围为[0,a/2)。
- 如果要把a变成b,那a%(a-b)显然是最可行的方案。如果a大于(a-b)的两倍,那就无法得到b了。
- 对于每一个要得到的b,如果a>=2*(a-b),显然a>=2*(a-(b+1)),即后续的b无法从a处转换,a这数没用了。但排列是从1-n一一对应的,这样一来就会空一个。
因此,从小到大将现有的数和目标检查,有一个不符合要求,输出-1即可。
代码:
D. Too Many Impostors(交互题)
题意:船员里混进一些假人。现在有n个人,n为3的倍数。每次你可以询问三个人的身份,如果假人多,回复0;如果真人多,回复1。输出所有假人的下标。
解:一开始想得蛮好的,令假人为0,真人为1,问交叠的两组4个人,如果全是1说明中间两人为1,balabala,在一堆Idleness limit exceeded on test 1后喜提WA。后来想想那不是还有两个人身份不确定嘛,多问几次是可以确定的。
考虑到有一个0一个1就可以确定任意一人的身份。现在先确定0和1的位置。n为3的倍数,暗示你每三人一组。找出答案为0和答案为1的组各一。
答案为0的可能情况 000 答案为1的可能情况 111
001 110
010 101
100 011 从答案为0的里面挑两个数a,b,和答案为1里的任意两个数c,d组合,如果答案为两个0,那么a=b=0,如果一个1一个0,那么第三个数为0。总之能确定下一个肯定为0的数。用同样的方法可以确定1。接下来判断这两组剩下的数,至多用8次询问。
接下来如果问n-6次显然会超,但是每一组其实用和上面差不多的方法问2次就可以了。如果这一组答案为0,那就和1组合,得到两个0说明前两个数为0,询问第三个数;得到一个1一个0说明第三个数为0,前两个数一1一0,问一个就能得到另一个。共询问n/3+8+(n/3-2)*2=n+4次,符合要求。
交互题关键在于找出特例。 --------某不知名大佬
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 10005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long int a[maxx]; int n; int ask(int x,int y,int z){ int t; printf("? %d %d %d\n",x,y,z); fflush(stdout); scanf("%d",&t); return t; } void ans(){ int cnt=0; for(int i=1;i<=n;i++) if(a[i]==0) cnt++; printf("! %d ",cnt); for(int i=1;i<=n;i++) if(a[i]==0) printf("%d ",i); printf("\n"); fflush(stdout); } signed main() { int T; scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=1;i<=n;i++) a[i]=-1; vector<int> res; int pos0,pos1; for(int i=1;i<=n-2;i+=3) { int t=ask(i,i+1,i+2); res.push_back(t); if(t==0) pos0=i; else pos1=i; } int s1=pos0,s2=pos1; int t1=ask(pos0,pos0+1,pos1+1); int t2=ask(pos0,pos0+1,pos1+2); if(!t1&&!t2) a[pos0]=a[pos0+1]=0; else a[pos0+2]=0,pos0=pos0+2; int t3=ask(pos0,pos1+1,pos1+2); if(t3==1) a[pos1+1]=a[pos1+2]=1,pos1=pos1+1; else a[pos1]=1; for(int i=s1;i<=s1+2;i++){ if(a[i]==-1) a[i]=ask(pos0,pos1,i); } for(int i=s2;i<=s2+2;i++){ if(a[i]==-1) a[i]=ask(pos0,pos1,i); } for(int i=0;i<=n/3-1;i++){ int p1=i*3+1,p2=i*3+2,p3=i*3+3; if(p1==s1||p1==s2) continue; if(res[i]==0){ int t=ask(pos1,p1,p2); if(t==0){ a[p1]=a[p2]=0; a[p3]=ask(pos0,pos1,p3); }else{ a[p1]=ask(pos0,pos1,p1); a[p2]=!a[p1];a[p3]=0; } }else{ int t=ask(pos0,p1,p2); if(t==1){ a[p1]=a[p2]=1; a[p3]=ask(pos0,pos1,p3); }else{ a[p1]=ask(pos0,pos1,p1); a[p2]=!a[p1];a[p3]=1; } } } ans(); } return 0; }