缝合怪

Posted on 2022-01-26 15:56  Capterlliar  阅读(135)  评论(0编辑  收藏  举报

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后就可以按正常进制转换做了。

代码:

View Code

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;
}
View Code

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;
}
View Code

 

BJTU1939  一颗姜会长多高?

题意:给出n个数,选y个拔到任意高度,剩下的数可以加x次,问最小值多少。(≤ n,1051≤ 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;
}
View Code

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即可。

代码:

View Code

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;
}
View Code