Codeforces Round #772 (Div. 2)思路分享

Codeforces Round #772 (Div. 2)

感谢老天爷,终于让我上紫了.....不过明天又是cf,真害怕自己这紫名还没捂热乎就没了....
不过,畏手畏脚就只能止步于此,只有不停地向前大踏步走,不计较得失才能更上一层楼。
总之,加油!!!我的目标是黑红!!!(有点夸大....,其实红名就很满足了。。)

A. Min Or Sum

你被给定一个长度为n的序列。你每次可以进行如下操作:选择序列中两个不同的元素\(a_i,a_j\)将其替换成\(x,y\),需要满足\(a_i|a_j=x|y\)。操作可以进行无数次,请问能得到的序列和最小是多少?
考虑某个元素\(a_i\),在二进制下若某位为1,那么最终这个1还是要保留的。但能将其他元素这一位上的1都消掉。所以不难发现,最小的答案的和为所有元素或起来的结果。

B. Avoid Local Maximums

给定一个长度为n的序列。你可以进行如下操作:选择序列中的某个元素将其替换成1到1e9中的任何一个元素。问最少进行多少次操作使得这个序列中不存在极大值?(极大值的定义如下:若\(1<=i<n\),且满足\(a_i>a_{i-1},a_i>a_{i+1}\))。
发现极大值之间若相隔超过两个元素以上的时候,我们用一次操作使某个极大值消失的时候,对另一个极大值是不影响的。但可能存在两个极大值之间只相隔一个元素(两个极大值之间不可能相邻)。我们先考虑只使用一次操作,怎么将极大值消失,可以将两边增大,或者中间减小,为了防止我们操作后产生新的极大值,我们可以使其相等。当两个极大值之间只相隔一个元素时,我们可以让两个极大值中间的那个元素取两个极大值的max,这样就能同时将两个极大值同时取消。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int T,n,a[N];  
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        int cnt=0;
        for(int i=2;i<n;++i)
        {
            if(a[i]>a[i-1]&&a[i]>a[i+1])
            {
                a[i+1]=max(a[i],a[i+2]);
                cnt++; 
            }
        }
        printf("%d\n",cnt);
        for(int i=1;i<=n;++i) printf("%d ",a[i]);
        puts("");
    }
    return 0;
}

C. Differential Sorting

给定一个长度为n的序列,你可以进行如下操作:选择序列中的某三个不同的元素\(a_i,a_j,a_k\)。然后将\(a_i\)替换成\(a_j+a_k\),问能否在进行不超过n次操作的前提下,使得整个序列变成非降序列。
首先发现最后两个元素无法改变。可以特判下最后元素的大小关系。同时我们可以注意到若存在某一个非负数,则我们可以选择这个非负数之前的元素为第二个元素,这个正数为第三个元素,之前的所有元素分别为第一个元素采用操作,就可以使得这个非负数之前的元素全部为非降的。之后我们倒着这个序列找非降序列,记录下找到的第一个非负数,然后之后按照上述操作即可。若找不到非负数,同样答案是NO.

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int T,n,a[N];  
inline bool check()
{
    for(int i=1;i<n;++i) if(a[i]>a[i+1]) return false;
    return true;
}
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        if(check()) {puts("0");continue;}
        if(a[n-1]>a[n]) {puts("-1");continue;}
        a[n+1]=2e9;
        int o=0,is=0;
        for(int i=n;i>=1;--i)
        {
            if(a[i]>a[i+1])
            {
                is=i+1;
                break;
            }
            if(a[i]>=0&&!o) o=i;
        }
        if(!o) {puts("-1");continue;}
        printf("%d\n",is-1);
        for(int i=is-1;i>=1;--i) printf("%d %d %d\n",i,is,o);
    }
    return 0;
}

D. Infinite Set

你被给定\(n\)个不同正整数,定义一个集合\(S\),他包含以下元素:
1.这n个正整数。
2.若\(x\)在集合中则\(2*x+1\)也在集合中。
3.若\(x\)在集合中,则\(4*x\)也在集合中。
问集合中严格小于\(2^p\)的数有多少个?
发现p的范围很大,根本不可能意义枚举,那我们只能找找这两个运算的规律,并且由于题目时\(2^p\)的形式,很容易让人想到二进制下的意义,两者分别代表向左移一位并且加1,向左移两位,那么严格小于\(2^p\)的限制就是最多p位。考虑只有一个数字时,我们每次可以选择向左移一加一,可以选择向左移二。很容易发现这其实是斐波那契数列。接下来由于有n个数字的缘故,还可能出现某个数字可能有其他数字生成,我们需要将这些数字去掉即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10,P=1e9+7;
int n,p,a[N],f[N],num,b[N];
ll sum[N];
map<int,bool>mp;
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d%d",&n,&p);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    f[1]=1;f[2]=2;
    for(int i=3;i<=p;++i) f[i]=(f[i-1]+f[i-2])%P;
    sum[0]=1;
    for(int i=1;i<=p;++i) sum[i]=(f[i]+sum[i-1])%P;
    sort(a+1,a+n+1);
    for(int i=1;i<=n;++i)
    {
        bool flag=true;
        int x=a[i];
        while(x)
        {
            if(mp.find(x)!=mp.end())
            {
                flag=false;
                break;
            }
            if(x&1) x>>=1;
            else if(x>=3&&(!(x&1))&&(!(x&2))) x>>=2;
            else break;
        }
        if(flag) b[++num]=a[i],mp[a[i]]=1;
    }
    ll ans=0;
    for(int i=1;i<=num;++i)
    {
        int mx=0;
        for(int j=30;j>=0;--j) if(b[i]&(1<<j)) 
        {
            mx=j;
            break;
        }
        if(mx>=p) continue;
        ans=(ans+sum[p-1-mx])%P;
    }
    printf("%lld\n",ans);
    return 0;
}

E. Cars

又是被我咕掉的一道题....(我还是太菜了,每次E都做不出来....)
之前一直再考虑并查集的做法,将每个车分成向左和向右两个点,然后无论是1还是2都将他们的反向连边,但发现这样的做法确实能淘汰掉部分不合法的情况,但用到的信息太片面,毕竟1和2还是不一样的,而且并查集还无法定位他们的位置信息,当时的我就这样咕咕咕咕咕...(遇到困难去睡大觉了。)
虽然我这个做法不对,但连边的想法确实是一种能表示他们关系的一种做法。我们尝试朴素的做法就是先不分开,就单独一个点表示一个车,然后连一个边表示他们存在方向相反。考虑每个点要么就是向左要么就是向右,显然这里的关系是只能相反的方向才能连边,相同方向的点是不能连边的。这个有点似曾相识吧?(啥啥啥,大雾)这不就是二分图吗?(我也是看题解的....)于是我们可以将整个图进行二分图判定,不是二分图的显然就不符合题意。(我们可以发现合法的答案将所以点都倒序且方向取反还是合法的,所以可以直接染色就行。)之后染完色的整个图,我们随意的让0代表向左,1代表向右,那么不就解决了所有点的去向问题的吗?之后考虑位置怎么搞,既然方向已经有了,那么我们重新看这限定条件,若存在1的关系,说明方向向左的带你位置小于方向向右的点。那么我们可以用有向边代替,存在2的关系则相反。最后直接跑拓扑就行。若存在环,也不合法。

点击查看代码
#include<bits/stdc++.h> 
using namespace std;
const int N=2e5+10;
int n,m,c[N],du[N],num,pre[N];
bool flag=true;
vector<int>son[N];
struct wy{int t,x,y;}a[N]; 
inline void dfs(int x)
{
    for(auto y:son[x])
    {
        if(c[y]!=-1&&c[y]==c[x]) flag=false;
        if(c[y]==-1)
        {
            c[y]=c[x]^1;
            dfs(y);
        }
    }
}
inline void topsort()
{
    queue<int>q;
    for(int i=1;i<=n;++i) if(du[i]==0) q.push(i);
    while(!q.empty())
    {
        int x=q.front();q.pop();
        pre[x]=++num;
        for(auto y:son[x]) if(--du[y]==0) q.push(y);
    }
}
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i) 
    {
        scanf("%d%d%d",&a[i].t,&a[i].x,&a[i].y);
        son[a[i].x].push_back(a[i].y);
        son[a[i].y].push_back(a[i].x);   
    }
    memset(c,-1,sizeof(c));
    for(int i=1;i<=n;++i) if(c[i]==-1) 
    {
        c[i]=0;
        dfs(i);
    }
    if(!flag) {puts("NO");return 0;}
    for(int i=1;i<=n;++i) son[i].clear();
    for(int i=1;i<=m;++i)
    {
        if((a[i].t==1&&c[a[i].x]==0)||(a[i].t==2&&c[a[i].x]==1)) son[a[i].x].push_back(a[i].y),du[a[i].y]++;
        else son[a[i].y].push_back(a[i].x),du[a[i].x]++;
    }
    topsort();
    if(num!=n) {puts("NO");return 0;}
    puts("YES");
    for(int i=1;i<=n;++i) printf("%c %d\n",c[i]?'R':'L',pre[i]);
    return 0;
}
posted @ 2022-02-21 21:40  逆天峰  阅读(115)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//