CF构造题练习记录

发现自己构造太菜了

于是决定上cf按tag找题来做

不定期更新

是CF上2000-2500的构造题

CF1506F

题意太长不翻译了(

 

 把原图路径画一画,大概是一堆这样的东西

构造路径的时候,按照斜线分组

如果两个点在同一组的话,那么看它是奇数还是偶数

奇数的话要把所有的路径变向,否则消耗为0

如果不在同一组,那么产生的消耗就是跨越组的消耗。

#include <bits/stdc++.h>
using namespace std;
struct Node{
    int r,c,d;
}a[200005];
int temp(Node a,Node b){
    return a.r<b.r;
};
int ans=0;
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int N;
        ans=0;
        scanf("%d",&N);
        a[0].r=1,a[0].c=1,a[0].d=0;
        for (int i=1;i<=N;i++)
            scanf("%d",&a[i].r);
        for (int i=1;i<=N;i++)
            scanf("%d",&a[i].c);
        sort(a+1,a+N+1,temp);
        for (int i=1;i<=N;i++)
            a[i].d=a[i].r-a[i].c;        
        for (int i=1;i<=N;i++){
            if (a[i].d==a[i-1].d){
                if (!(a[i].d%2))
                    ans+=a[i].c-a[i-1].c;
            }
            else{
                if (a[i-1].d%2){
                    ans+=(a[i].d-a[i-1].d+1)/2;
                }
                else ans+=(a[i].d-a[i-1].d)/2;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
} 

 CF925C

题意:

给你一堆数字,排列这些数字,让他们前缀异或和递增

sol:

考虑当前获得了异或和$sum$

那么,对下一个数字的情况做分类讨论

情况1:下一个数字位数比自己高

那么肯定比这个大

情况2:

下一个数字和这个数字的最高位一样,都是$1$

那么显然是变小的

情况3:

下一个数字和$sum$在最高位不同,且$sum$在某个位置上是$0$

那么,其实我们找到一个最高位是这个位置的数字,把它放上去就好了,新的数字肯定比这个数字大

那么我们就得到了一个做法

枚举$N$个位置,记录当前的$sum$,每次从低位到高位考虑,找到一个$0$,找最高位为这个位置的数字,有就塞,没有就继续找就行了。

#include <bits/stdc++.h>
using namespace std;
int N;
long long  a[100005];
vector<long long> anss;
queue<long long >bit[65]; 
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        scanf("%lld",&a[i]);
        for (int j=60;j>=0;j--)
            if ((a[i]>>j)&1ll) {
            bit[j].push(a[i]);
            break;
        }
    }
    sort(a+1,a+N+1);
    long long  ans=0;
    for (int i=1;i<=N;i++){
        bool flag=false;
        for (int j=0;j<=60;j++){
            if (!((ans>>j)&1ll)&&!(bit[j].empty())) {
                long long x=bit[j].front();
                bit[j].pop();
                ans^=x;
                flag=true;
                anss.push_back(x);
                break;
            }
        }
        if (!flag) {
            printf("No\n");
            return 0;
        }
    }
    printf("Yes\n");
    for (auto x:anss){
        printf("%lld ",x);
    }
    return 0;
}

 CF1513D

题意:

$gcd(a_i,a_{i+1}...a_j)==min(a_i,a_{i+1}...a_j)==min(a_i,a_{i+1}...a_j)$,则有一条$i -> j$的边,权值为这个$gcd$

对每个$i -> i+1$还有一条长度为$p$的边

问最小生成树权值

sol:
考虑krusual的过程,问题的瓶颈在于找到这个最小值

我们可以从小到大的枚举这个最小值,并且向两边扩展,进行krusual的过程

然后可以发现,每个点只会被访问到一次

为什么?

因为我们是从小到大的枚举的$a_i$,如果一个$a_i$在被访问过还能被扩展的话,那么显然,这个被扩展的数可以被扩展这个数的数继续扩展出去。

然后就暴力模拟这个过程即可。

如果$a_i > p$ ,按$p$一个个连过去就行

$O(n log n)$ ,$log$是并查集

#include <bits/stdc++.h>
using namespace std;
int N;
long long p,b[200005],ans;
bool pd[200005],vis[200005];
int fa[200005];
struct Node{
    int pos;
    long long val;
}a[200005];
bool temp(Node a,Node b){
    return a.val<b.val;
}
int Getfa(int x){
    return (x==fa[x])?x:fa[x]=Getfa(fa[x]);
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
    ans=0;
    scanf("%d%lld",&N,&p);
    for (int i=1;i<=N;i++){
        scanf("%lld",&a[i].val);
        b[i]=a[i].val;
        a[i].pos=i;
        fa[i]=i;
    }
    sort(a+1,a+N+1,temp);
    for (int i=1;i<=N;i++){
        if (vis[a[i].pos]) continue;
        int rt=a[i].pos;
        int x=a[i].pos-1;
        long long nww=a[i].val;
        if (nww>=p) break;
        while (x>0&&__gcd(nww,b[x])==a[i].val){
            if (Getfa(rt)==Getfa(x)) {x--;continue;}
            else fa[Getfa(x)]=Getfa(rt),ans+=nww;
            vis[x]=true;
            x--;
        }
        x=a[i].pos+1;
        nww=a[i].val;
        while (x<=N&&__gcd(nww,b[x])==a[i].val){
            if (Getfa(rt)==Getfa(x)) {x++;continue;}
            else fa[Getfa(x)]=Getfa(rt),ans+=nww;
            vis[x]=true;
        x++;
        }
    }
    for (int i=1;i<N;i++)
        if (Getfa(i)==Getfa(i+1)) continue;
        else{
            ans+=p;
            fa[Getfa(i)]=Getfa(i+1);
        }
    for (int i=1;i<=N;i++)
        pd[i]=vis[i]=false;
        printf("%lld\n",ans);
    }
    return 0;
}

 CF1494E

 

题意:

$n$个点,有向图,每个边上有一个字母

动态增加删除边,每次给定$k$,问有没有一种方案能找到一条$k$个点的序列,按照序列跑图上的边,正跑反跑是同一个字符串

sol:

发现这个定义有点类似于回文串,于是考虑分奇偶讨论

为了讨论的方便,我分析的时候是先把边权扔到点上,然后再重新接回去的

对于长度为奇数的列来说

我们发现,如果出现$A ---- C ---- A$这样形式的点权,一定是可以形成回文串的

只需不断遍历$A C A C A ... $即可

考虑把点权放回边上

其实就是两个为$A$的缩成一个点,变成两个点之间的边是双向边即可。

对于偶数的情况:

出现$A----A$这样的形式即可。

那么意思就是,存在两个点之间是双向边且双向边上字符相等

至于其他可以形成回文串的情况

其实我们可以发现,如果你能形成别的回文串,那么对于中心字符来说,一定是满足上面这些条件的

所以这东西是充分必要的

暴力跑这个即可。

#include <bits/stdc++.h>
using namespace std;
map<pair<int,int>,char>col; 
int N,T;
int eans,oans;
int main(){
    cin>>N>>T;
    while (T--){
        char opt;
        cin>>opt;
        if (opt=='+'){
            int u,v;
            char c;
            cin>>u>>v;
            cin>>c;
            pair<int,int> nw={u,v};
            col[nw]=c;
            nw=make_pair(v,u);
            if (col.find(nw)==col.end()||col[nw]=='?') continue;
            oans++;
            if (col[nw]==c){
                eans++;
            }
        }else
        if (opt=='-'){
            int u,v;
            char c;
            cin>>u>>v;
            pair<int,int> nw=make_pair(u,v);
            c=col[nw];
            col[nw]='?';
            nw=make_pair(v,u);
            if (col.find(nw)==col.end()||col[nw]=='?') continue;
            oans--;
            if (col[nw]==c){
                eans--;
            }
        }else
        if (opt=='?'){
            int k;
            cin>>k;
            getchar();
            if (k&1) {
                if (eans||oans) printf("YES\n");
                else printf("NO\n");
            }
            else{
                if (eans) printf("YES\n");
                else printf("NO\n");
            }
        }
    }
    return 0;
}

 CF1559D2

好神仙的题(

 

官方的做法没太看懂,看到了一个很厉害的做法

考虑最终答案的局面,一定有一个最大的树,我们称之为$rt$集合

于是我们随便指定一个点,让它作为最大的那个树中的元素,其他点优先向它连边

接下来分类讨论,考虑一个点

如果两边都不在各自的$rt$集合里,那么直接向$rt$接上就好了。

否则一定是一边在$rt$中,一边不在$rt$中,这种情况我们后续讨论

还有一种两边都已经在$rt$中,不管就好了。

然后对于那些一边在$rt$中,一边不在$rt$中的

考虑从左边的向右边的连边

为什么这是对的呢?

因为这两个集合是完全不交叉的(定义)

如果连边了,就意味着我选择了其中的一边,加入了$rt$集合里

我们贪心的选择,能加入$rt$集合就加入,如果不能加入$rt$集合的一定在也没有办法加入$rt$集合,因为冲突了。

这样的话我们就保证了$rt$集合尽可能地大,也就是散块尽可能地少。

也就保证了生成树中没有被接上的边最少,也就保证了接上的边最多。

#include <bits/stdc++.h>
using namespace std;
int N,M1,M2;
queue<int> a;
queue<int> b;
int fa1[100005],fa2[100005];
vector<pair<int,int> >ans;
void init(){
    for (int i=1;i<=N;i++) fa1[i]=fa2[i]=i;
}
int Getfa1(int x){ return (x==fa1[x])?x:fa1[x]=Getfa1(fa1[x]); }
int Getfa2(int x){ return (x==fa2[x])?x:fa2[x]=Getfa2(fa2[x]); }
void Merge1(int x,int y){
    int fx=Getfa1(x),fy=Getfa1(y);
    fa1[fx]=fy;
}
void Merge2(int x,int y){
    int fx=Getfa2(x),fy=Getfa2(y);
    fa2[fx]=fy;
}
int main(){
    scanf("%d%d%d",&N,&M1,&M2);
    init();
    for (int i=1;i<=M1;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        Merge1(u,v);
    }
    for (int i=1;i<=M2;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        Merge2(u,v);
    }
    int rt=1;
    for (int i=1;i<=N;i++){
        int rt1=Getfa1(rt),rt2=Getfa2(rt);
        int fx=Getfa1(i),fy=Getfa2(i);
        if (rt1!=fx && rt2!=fy){
            Merge1(rt1,i),Merge2(rt2,i);
            ans.push_back({rt,i});
        }
        else if (rt1!=fx && rt2==fy) 
            a.push(i);
        else if (rt1==fx && rt2!=fy)
            b.push(i);
    }
    while (!a.empty() && !b.empty()){
        while (!a.empty()){
            int x=a.front();
            if (Getfa1(x)==Getfa1(rt)) a.pop();
            else break;
        }
        while (!b.empty()){
            int x=b.front();
            if (Getfa2(x)==Getfa2(rt)) b.pop();
            else break;
        }
        if (a.empty()||b.empty()) break;
        int x=a.front(),y=b.front();
        a.pop(),b.pop();
        Merge1(x,y),Merge2(x,y);
        ans.push_back({x,y});
    }
    printf("%d\n",ans.size());
    for (auto nw:ans){
        printf("%d %d\n",nw.first,nw.second);
    }
    return 0;
}

 CF1659D

挺好玩的题。

考虑一个位置,如果它填入$1$的话,那么必然,这个位置会被累加$i$次(在对这个位置进行$sort$之前)

然后再考虑这个位置被$sort$后,显然,在后续的$c_i - i$轮中,这个位置一定也是$1$

那么,其实意思就是,在第$i+c_i-i+1$轮的时候,这个被$sort$过的序列恰好有$i$个$0$在最前面

也就是说,第$c_i +1$个位置一定是个$0$。

也就是说,如果一个位置$i$填入$1$,那么第$c_i +1$个位置就是$0$

接下来考虑$0$,如果一个位置填入$0$的话,前$i-1$轮这个位置都是$0$,第$i-1$~$i+ c_i -1$轮这个位置是$1$,之后是$0$

于是条件就是,第$i$个位置如果是$0$的话,那么第$i+c_i$个位置就是$1$

然后就不断地扫描,遇到一个位置,如果它没有被访问过的话,就填入$1$,然后更新后面所有的$0$,直到没有位置更新为止。

#include <bits/stdc++.h>
using namespace std;
int T,N;
int c[200005],a[200005];
bool vis[200005];
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            scanf("%d",&c[i]);
            vis[i]=false;
        }
        for (int i=1;i<=N;i++){
            if (!vis[i]){
                if (c[i]==0) a[i]=0;
                else{
                    int x=c[i]+1;
                    a[i]=1;
                    vis[i]=true;
                    while (x<=N&&!vis[x]){
                        vis[x]=true;
                        a[x]=0;
                        x=x+c[x];
                    }
                }
            }
        }
        for (int i=1;i<=N;i++)
            printf("%d ",a[i]);
        printf("\n");
    }
    return 0;
}

 CF1787E

https://codeforces.com/contest/1787/problem/E

考虑答案可能存在的必要条件:

前$n$个自然数的异或和和$k$个$x$的异或和相同。

然后考虑最多能分几个组

显然每次把$(x,x xor a)$分在一起是最多组的。

剩下无法分配的放在一块即可。

如果够分就有解,输出即可。

#include <bits/stdc++.h>
using namespace std;
int N,M,K;
bool pd[200005];
int main(){
    int T;
    cin>>T;
    while (T--){
        scanf("%d%d%d",&N,&K,&M);
        int xs = 0;
        if (N%4 == 0) xs = N;
        if (N%4 == 1) xs = 1;
        if (N%4 == 2) xs = N+1;
        if (N%4 == 3) xs = 0;
        int xs1 = 0;
        if (K&1) xs1=M;
        else xs1=0;
        if (xs != xs1) {
            printf("NO\n");
            continue;
        }
        for (int i = 1 ; i <= N ;i ++)
            pd[i] =false;
        int cnt = 0;
        for (int i = 1 ; i <= N ; i ++){
            if (!pd[i] && (i ^ M) <= N){
                cnt ++ ;
                pd[i] = true;
                pd[i^M] = true;
            }
        }
        if (cnt >= K-1){
            printf("YES\n");
            for (int i = 1 ; i <= N ; i ++) pd[i] = false;
            int cnt = 0;
            int cnt1 = 0;
            for (int i = 1 ; i <= N ; i ++){
                if (cnt == K-1 ) break;
                if (!pd[i] && (i^M) <= N){
                    cnt ++;
                    pd[i] = true;
                    pd[i^M] = true;
                    if (i == M) {cnt1++;printf("1 %d\n",i);}
                    else{
                        cnt1 += 2;
                        printf("2 %d %d\n",i,i^M);
                    }
                }
            }
            printf("%d ",N-cnt1);
            for (int i = 1 ; i <= N ; i ++){
                if (!pd[i]) printf("%d ",i);
            }
            printf("\n");
        }else printf("NO\n");
    }
    return 0;
}

 CF1730D

https://codeforces.com/contest/1730/problem/D

首先有一个结论:

$s_1$中的$i$位置和$s_2$中的$n-i-1$位置是对应的,并且它们始终是对称的。

考虑交换过程会发现,这两个值之间的对称关系不改变,因为交换一定是前缀和后缀交换。

并且,我们可以发现,这两个位置上的值是可以互相交换的。

具体操作是,先选$i$,把$t_{n-i-1} .. t_n$换到$s_1$的前缀,然后再选$i-1$。

而组与组之间的相对位置是可以交换的。

先证明$s_1$和$s_n$可以交换。

先选$1$,把$s_1$放到$t_n$

然后交换$s$和$t$,再选一次$1$,这样就会把$s_n$放到首位。

然后我们可以发现,其实我们每次都可以通过操作把需要交换的组分别放到头和尾。

这样就说明了相对位置可以交换。

于是问题就变成了,如果组是$<a,a>$这样的话,长度是奇数的时候,允许有一组$<a,a>$的数量为奇数存在(放在中心)

其余的所有组的数量都是偶数。

写个map就好了

#include <bits/stdc++.h>
using namespace std;
map<pair<char,char> , int> mp; 
int main(){
    int T;
    cin>>T;
    while (T--){
        mp.clear();
        int Len=0;
        cin>>Len;
        string s1,s2;
        cin>>s1>>s2;
        for (int i = 0; i  < Len ; i ++){
            char c1 = s1[i],c2=s2[Len-i-1];
            char c3,c4;
            c3=min(c1,c2);c4=max(c1,c2);
            mp[{c3,c4}]++;
        }
        int cnt1 = 0;
        bool flag = false;
        for (auto x:mp){
            pair<char,char> pa;
            pa = x.first;
            if (pa.first == pa.second && (x.second & 1)){cnt1 ++;continue;} 
            if (x.second & 1) {
                flag = true;
                break;
            }
        }
        if (!flag){
            if (cnt1 == 0 || (cnt1 == 1 && (Len&1)))
                printf("YES\n");
            else printf("NO\n");
        }else printf("NO\n");
    }
    return 0;
}

 

posted @ 2022-03-10 00:38  si_nian  阅读(101)  评论(0)    收藏  举报