Codeforces Round #649 (Div. 2) / contest 1364


ABCDE

( √:做出; ●:尝试未做出; ○:已补题 )


题目地址:https://codeforces.com/contest/1364

这一次做得好差…… 😣(ಠ_ಠ)



A XXXXX

题意:给出一个数组 a,一个整数 x,求最长的 a 的子数组(连续),使得这个子数组的和不能被 x 整除,并给出这个长度;如果不存在,输出 -1 。

思路:好吧这道题居然卡了我二十分钟。我的思路是求一个前缀和,再把每个前缀和取模 x,那么问题就变为在一个数组中找到间距最大的不相等的两个元素的间距。后来发现,最优答案一定要么是下标 0 和最右的不和 s[0] 相等的下标,要么是下标 n 和最左边不和 s[n] 相等的下标。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
int s[maxn],n,x;

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        mem(s,0);
        n=read(); x=read();
        REP(i,1,n) s[i]=s[i-1]+read();
        REP(i,1,n) s[i]%=x;
        int ans=-1;
        int j=n;
        while(j>=0 && s[j]==s[0]) j--;
        if(j>=0) ans=max(ans,j-0);
        j=0;
        while(j<=n && s[j]==s[n]) j++;
        if(j<=n) ans=max(ans,n-j);
        printf("%d\n",ans);
    }

    return 0;
}



B Most socially-distanced subsequence

题意:给出一个 n 的全排列 p,要找出 k 个数 s 1 , s 2 , . . . , s k s_1,s_2,...,s_k s1,s2,...,sk ,使得 ∣ s 1 − s 2 ∣ + ∣ s 2 − s 3 ∣ + . . . . + ∣ s k − 1 − s k ∣ |s_1-s_2|+|s_2-s_3|+....+|s_{k-1}-s_k| s1s2+s2s3+....+sk1sk 最大,并且 k 最小。

思路:这个显然就是 p 的首尾两点以及所有拐点拿出来。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
int a[maxn],n,ans[maxn];

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        n=read();
        REP(i,1,n) a[i]=read();
        int i=1,j=2,tot=0;
        ans[tot++]=i;
        while(1)
        {
            while(j<n && (a[i+1]>a[i])^(a[j+1]<a[j])) j++;
            ans[tot++]=j;
            if(j>=n) break;
            i=j++;
        }
        printf("%d\n",tot);
        REP(i,0,tot-1) printf("%d ",a[ans[i]]);
        puts("");
    }

    return 0;
}



C Ehab and Prefix MEXs

题意:定义 MEX(S) 表示最小的不在集合 S 中的非负整数,现给出数组 a,要求构造数组 b,使得对于任意 i, M E X ( { b 1 , b 2 , . . . , b i } ) = a i MEX(\{b_1,b_2,...,b_i\})=a_i MEX({b1,b2,...,bi})=ai 。给出的数组 a 保证 0 ≤ a i ≤ i 0\le a_i \le i 0aii,且 a i ≤ a i + 1 a_i\le a_{i+1} aiai+1

思路:首先对于 a[i+1]>a[i] 的位置,一定有 b[i+1]=a[i],因为在 i 以及之前的 b 不能有 a[i]。然后填完所有这些位置,再从小到大从前往后填没填过的就可以了。这道题不存在构造不出的情况。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=2e5+5;
int a[maxn],b[maxn],n,v[maxn];

int main()
{
    //freopen("input.txt","r",stdin);
    n=read();
    REP(i,1,n) a[i]=read(),b[i]=-1;
    REP(i,1,n-1) if(a[i+1]>a[i]) b[i+1]=a[i],v[a[i]]=1;
    b[n+1]=a[n]; v[a[n]]=1;
    int j=0;
    REP(i,1,n) if(b[i]==-1)
    {
        while(v[j]) j++;
        b[i]=j; v[j]=1;
    }
    REP(i,1,n) printf("%d ",b[i]);

    return 0;
}



D Ehab’s Last Corollary

题意:给出一个 n 个结点的连通无向图,以及一个正整数 k,要解决下面两个问题之一:

  • 找出一个恰有 ⌈ k 2 ⌉ \lceil\frac{k}{2} \rceil 2k 个结点的独立集(两两没有边相连);
  • 找出一个简单环,长度不大于 k 。

思路:我比赛的时候一直朝着边双连通分量的思路在想,所以怎么也想不出来。

其实思路很简单,如果存在一个极小环(也等价于存在一个环,因为可以从一个环中获得一个极小环,极小环的意思就是环中任意不相邻的两点没有连边,所以这个极小环中不存在更小的环),那么对于任意的 k,一定满足上述条件之一,因为假设这个环的长度为 L,如果 L ≤ k L\le k Lk,那么可以解决第二个问题,否则,只用间隔输出环上的结点,就可以解决第一个问题;如果这个图不存在极小环,那么这个图就是一棵树,用 01 染色的方法处理后,就可以解决第一个问题。

然后就是找极小环有点麻烦,思路类似 Tarjan 算法,dfs 这个图的时候记录每个结点的 dfs 序,并且用一个栈记录遍历路径,然后遇到某个结点有反向边的时候,找出反向边中终点 dfn 最大的,弹栈到这个终点就得到一个极小环。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
vector<int> G[maxn],c;
int n,m,k,co[maxn],dfn[maxn],cnt,flag;
stack<int> s;

void dfs(int u,int fa)
{
    if(flag) return;
    dfn[u]=++cnt;
    s.push(u);
    int maxx=0,w;
    for(int v:G[u]) if(v!=fa && dfn[v]) maxx=max(maxx,dfn[v]),w=v;
    if(maxx)
    {
        while(1)
        {
            int x=s.top(); s.pop();
            c.push_back(x);
            if(x==w) break;
        }
        flag=1;
        return;
    }
    for(int v:G[u]) if(v!=fa && !dfn[v])
    {
        if(flag) return;
        dfs(v,u);
    }
    if(!s.empty()) s.pop();
}

void dfs_co(int u,int fa)
{
    if(!fa) co[u]=0;
    for(int v:G[u]) if(v!=fa)
    {
        co[v]=co[u]^1;
        dfs_co(v,u);
    }
}

int main()
{
    //freopen("input.txt","r",stdin);
    n=read(),m=read(),k=read();
    REP(i,1,m)
    {
        int u=read(),v=read();
        G[u].push_back(v);
        G[v].push_back(u);
    }
    if(m==n-1)
    {
        dfs_co(1,0);
        int num1=0;
        REP(i,1,n) num1+=co[i];
        int q=(num1>=(k+1)/2)?1:0;
        puts("1");
        for(int i=1,j=1;i<=n && j<=(k+1)/2;i++)
            if(co[i]==q) printf("%d ",i),j++;
        return 0;
    }
    dfs(1,0);
    //for(int i:c) cout<<i<<' ';
    if(c.size()<=k)
    {
        puts("2");
        printf("%d\n",c.size());
        for(int i:c) printf("%d ",i);
    }
    else
    {
        puts("1");
        for(int i=0,j=1;j<=(k+1)/2;i+=2,j++)
            printf("%d ",c[i]);
    }

    return 0;
}



E X-OR

题意:这是一道交互题。有一个长度为 n 的 0~(n-1) 的全排列 p( 3 ≤ n ≤ 2048 3\le n\le 2048 3n2048),你可以询问至多 4269 次,每次给出 i 和 j,系统会返回 p i ∣ p j p_i|p_j pipj ,你需要求出这个全排列。

思路:题解真是太厉害了。核心思想在于,如何用 logn 的复杂度计算出某一个的值,假设现在存在一个函数 f(i),可以至多询问 logn 次就计算出 p[i],那么我们只需要遍历一次就可以求出 0 的下标:维护变量 u 和 t (t=p[u]),每遇到 query(i, u)==t 时,说明 p[i] 是 p[u] 的(二进制)子集,就用 i 和 p[i] 更新 u 和 t,最后一定可以使 u 为 0 的下标。得到 0 的位置之后,再遍历一遍就可以计算出每个位置的值。

那么 f 怎么搞?

定义数组 z 满足, p z i p_{z_i} pzi 的第 i 位为 0,那么 f ( u ) = q u e r y ( u , z 0 ) ⋀ . . . ⋀ q u e r y ( u , z k − 1 ) f(u)=query(u,z_0)\bigwedge ...\bigwedge query(u,z_{k-1}) f(u)=query(u,z0)...query(u,zk1) ,其中 k 为最大的位数。因为不管 p u p_u pu 的第 i 位是 1 还是 0,f(u) 算出来之后都可以得到一样的结果。

而 z 数组就循环若干次随机询问就可以构造出来啦。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int query(int i,int j)
{
    printf("? %d %d\n",i,j);
    fflush(stdout);
    return read();
}

int z[11],n,k,tot,p[3000];
int getP(int j)
{
    int ret=0xffff;
    REP(i,0,k-1)
    {
        if(j!=z[i]) ret&=query(j,z[i]);
        else ret&=(0xffff-(1<<i));
    }
    return ret;
}

int main()
{
    //freopen("input.txt","r",stdin);
    srand(time(0));
    n=read();
    k=log2(n-1)+1;
    while(tot<k)
    {
        int i=rand()%n+1,j=rand()%n+1;
        while(i==j) i=rand()%n+1,j=rand()%n+1;
        int x=query(i,j);
        for(int v=0;v<k;v++) if(!z[v] && !((1<<v)&x)) tot++,z[v]=i;
    }
    int u=1,t=getP(u);
    REP(i,2,n)
    {
        int x=query(u,i);
        if(x==t) u=i,t=getP(i);
    }
    REP(i,1,n) if(i!=u) p[i]=query(i,u);
    printf("! ");
    REP(i,1,n) printf("%d ",p[i]);
    puts(""); fflush(stdout);

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值