Codeforces Round #779 (Div. 2) 记录

本文探讨了如何运用数学思维解决编程竞赛中的难题,通过分析最大公约数、排列组合和博弈论等概念,展示了解题策略。在Marin and Anti-coprime Permutation问题中,证明了最大公约数不超过2的必要性和充分性;在Shinju and the Lost Permutation题目中,利用拓扑排序确定排列条件;D题中,通过异或操作和位运算找到满足条件的x值;E题Gojou and Matrix Game涉及博弈论和绝对值不等式;最后,F题Juju and Binary String解析了字符串滑动窗口的平均值问题。文章深入浅出地阐述了数学在编程竞赛中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Marin and Anti-coprime Permutation(800)在这里插入图片描述在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

思路(规律的做法)

  • 场上很多人过,而自己完全没思路
  • 划分解空间:考虑最大公约数可以取哪些值
    • 好像这种做不出来的可行的解通常个数很少??
  • 可以多写几个排列发现规律。最终发现最大公约数只可能小于等于2.难度一下降低了!
  • 当最大公约数等于2时,只有n为偶数的时候可以满足,并且奇数与偶数配对,偶数与奇数配对。每个n!2\frac{n!}{2}2n!种。

int main()
{
    int t;
    cin >> t;
    for(int  i = 0;i<t;i++)
    {
        int n; cin >> n;
        if(n%2) cout << 0 << endl;
        else
        {
            ll a = 1;
            for(long long i = 1;i<=n/2;i++) a = (a*i)%mod;
            a = (a % mod) * (a % mod) % mod;
            cout << a << endl;
        }
    }
    system("pause");
}

证明g=gcd(1⋅p1,2⋅p2⋯n⋅pn)≤2g = gcd(1 \cdot p_1,2\cdot p_2 \cdots n \cdot p_n) \leq 2g=gcd(1p1,2p2npn)2(GOOD)

  • 有了上述的猜想,说不定可以顺水推舟完成证明。
  • 如果g=2k,k>1g = 2^k,k>1g=2k,k>1,那么1⋅p1,2⋅p2⋯n⋅pn1 \cdot p_1,2\cdot p_2 \cdots n \cdot p_n1p1,2p2npn都是偶数,只能按照上述思路中的配对方式进行配对。这时p2p_2p2是奇数,不含有2的因子,因此2⋅p22 \cdot p_22p2不是2的幂次(幂次大于1)的形式
  • 否则,g不是2k2^k2k,且k>1的形式。则存在质数p>2p>2p>2使得g含有p的因子,即p∣gp|gpg。在1-n的排列中仅有⌊np⌋\lfloor \frac{n}{p} \rfloorpn能被p整除。在这些满足条件的数中,由于p是质数,仅仅自身与某个不同的这样的数配对而产生的乘积能被p整除,因此只有2⌊np⌋2\lfloor \frac{n}{p} \rfloor2pn个配对。由于p≥3p \geq 3p3,因此配对最多有2⌊n3⌋<n2\lfloor \frac{n}{3} \rfloor<n23n<n个,即不存在配对方式使得所有配对(n个配对)的乘积含有因子p,自然不含有因子p;

Shinju and the Lost Permutation(1700-必要条件也是充分条件)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

  • 寻找特殊值:最大值排在第一个对应的power必为1,其余的都>1;等价地可以从c为1开始的值开始构建排列
  • 考察两个邻接的c。必要条件ci+1−ci≤1c_{i+1} - c_i \leq 1ci+1ci1,也是充分的,可以使用拓扑排序证明。(只需要证明拓扑排序存在即可 即不会形成环路)
int main()
{
    int t; 
    cin >> t;
    for(int i = 0;i<t;i++)
    {
        int n;
        cin >> n;
        int c[n];
        int index1 = -1;
        int count1 = 0;
        for(int j = 0;j<n;j++) 
        {
            cin>>c[j];
            if(c[j] == 1) {count1++;index1 = j;}
        }
        if(count1>1||count1<=0) cout<<"NO"<<endl;
        else
        {
            count1=0;
            int j = index1;
            int last = c[j];
            j=(j+1)%n;
            while(count1<n-1)
            {
                if(c[j]-last<=1)
                {
                    last = c[j];
                    j=(j+1)%n;
                    count1++;
                }
                else
                {
                    cout<<"NO"<<endl;
                    break;
                }
            }
            if(count1 == n-1)cout<<"YES"<<endl;
        }
    }
    system("pause");
}

D题

388535 (Easy Version-1600)(必要条件也是充分条件)

在这里插入图片描述在这里插入图片描述

思路

  • 未想到的点:将二进制比特稍微列一下然后可以顺水推舟解决问题。思维的断点:注意经过异或后a数组的特殊性,自然需要考虑下原排列。
  • 异或运算的结合律(a⊕x)⊕x=a(a \oplus x) \oplus x = a(ax)x=a,异或运算如果与0异或起到保留作用,与1异或起到取反作用
  • 对于处理后的a数组每个元素每一个二进制位,考察1的个数的总和(必要条件),来构造x:
    • 如果该位1的个数总和不等于原始a(未经过异或)的1的个数总和,说明该位与x异或后一定进行了翻转,那么只有将x的该位设为1才能保证异或后的a数组每个元素与构造出的x异或后与原始a数组对应位的1的个数总和相同。
    • 否则,该位中0的个数总和等于原始a中该位的0的个数总和。
      • 如果1的个数不等于0的个数,x的该位必须置0,分析同理
      • 如果1的个数等于0的个数,则x从该位往后的位都可以任意取0或1.这是因为a此时元素的个数一定为2的次幂形式,从该位往后原始a中元素中每一位0的个数和1的个数均相同,当然对异或后的a也一样。
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>

using namespace std;
const int mod = 998244353;  
typedef long long ll;
 

int main()
{
    int t; 
    cin >> t;
    for(int i = 0;i<t;i++)
    {
       int l,r;
        cin >> l >> r;
        int a_r[19]; memset(a_r,0,19*sizeof(int));
        int a_c[19];memset(a_c,0,19*sizeof(int));
        for(int k  =  1;k<=r-l+1;k++)
        {
            int a; cin >> a;
            int j = 1;
            while(a>0)
            {
                a_r[j]+=a&1;   //最低位
                a=a>>1;
                j++;
            }
            int o =k-1;
            j =1;
            while(o>0)
            {
                a_c[j]+=o&1;
                o=o>>1;
                j++;
            }
        }
        //统计完成后构造x
        int x= 0;
        for(int j = 1;j<19;j++)
        {
            if(a_c[j]!=a_r[j]) x+=(1 << (j-1));
        }
        cout << x << endl;
    }
    system("pause");
}

388535 (HARD Version)(必要条件也是充分条件)在这里插入图片描述在这里插入图片描述

在这里插入图片描述

  • 较为复杂的解法:只需要考虑easy version中0和1相等的情况该取0还是取1;

My Solution

  • 当诸位确定x的取值时,记录取到的每个由a变换过来的前缀prefix。当遇到0和1相等的时候两种情况:
    • 如果prefix全部相等,需要分别令当前位取1,取0,分两支搜索,且分支搜索后两个都不会完全相同。看最终结果哪个满足条件
    • 否则prefix不等,继续循环即可。
    • 下述代码有很多重复冗余,但还没想好咋优化。。。。
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>

using namespace std;
const int mod = 998244353;  
typedef long long ll;






int main()
{
    int t;cin>>t;
    for(int i  = 0;i<t;i++)
    {
        int l,r; cin >> l >> r;
        int a[r-l+1];
        memset(a,0,(r-l+1)*sizeof(int));
        int a_e[18];
        int a_a[18];   //1到17位放置
        memset(a_e,0,18*sizeof(int));
        memset(a_a,0,18*sizeof(int));
        for(int j = l;j<=r;j++)
        {   
            int c; cin >> c;
            a[j-l]=c;
            int k = 16;  //从最高位开始放置
            while(k>=0)
            {
                a_e[17-k]+=((j>>k)&1);  //统计排列从低位到高位的1的个数情况
                a_a[17-k]+=((c>>k)&1);  //统计现有数据从最高位到最低位1的个数
                k--;
            }
        }
        int x = 0,j=1;  //构造x,从最高位开始逐层往后构造
        int prefix[r-l+1];
        memset(prefix,0,(r-l+1)*sizeof(int));
        bool flag = false;
        while(j<=17)
        {
            if(a_e[j]!=a_a[j]) 
            {
                x+=(1<<(17-j));
                for(int k = 0;k<r-l+1;k++) prefix[k]=(prefix[k]<<1)+(1^((a[k] >> (17-j))&1));
            }
            else    //a_e[j]==a_a[j]
            {
                if((r-l+1)-a_e[j]!=a_e[j])
                {
                    x+=(0<<(17-j));
                    for(int k = 0;k<r-l+1;k++) prefix[k]=(prefix[k]<<1)+(0^((a[k] >> (17-j))&1));
                }
                else    //1的个数和0的个数相同
                {
                    bool allequal = true;   //是否所有都相同
                    int maxi = prefix[0];
                    for(int k = 1;k<r-l+1;k++)
                    {
                        if(prefix[k]!=prefix[k-1]) allequal = false;
                        maxi = max(maxi,prefix[k]);
                    }
                    if(!allequal)
                    {
                                                    //找出所有的最大值,如果1的个数和0的个数不相同
                                                    //则需要让0的个数比1的个数多
                        int count1 = 0,count0 = 0;
                        for(int k = 0;k<r-l+1;k++)
                        {
                            if(prefix[k]==maxi)   //找出所有最大值的k
                            {
                                count1+=(a[k]>>(17-j))&1;
                                count0+=(!((a[k]>>(17-j))&1));
                            }
                        }
                        int add = 0;
                        if(count0>count1) add=0; else add=1;
                        //更新prefix前缀
                        x+=(add<<(17-j));
                        for(int k = 0;k<r-l+1;k++) prefix[k]=(prefix[k]<<1)+(add^((a[k] >> (17-j))&1));
                    }
                    else {flag = false;break;}
                }
            }
            j++;
        }
        if(!flag)
        {
            int x_1 = x+(1<<(17-j));
            int prefix1[r-l+1];
            for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix[k]<<1)+(1^((a[k] >> (17-j))&1));
            int j_1 = j;
            j++;
            while(j<=17)
            {
                if(a_e[j]!=a_a[j]) 
                {
                    x_1+=(1<<(17-j));
                    for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(1^((a[k] >> (17-j))&1));
                }
                else
                {
                    if((r-l+1)-a_e[j]!=a_e[j])
                    {
                        x_1+=(0<<(17-j));
                        for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(0^((a[k] >> (17-j))&1));
                    }
                    else
                    {
                        int maxi = prefix1[0];
                        for(int k = 1;k<r-l+1;k++)
                        {
                            maxi = max(maxi,prefix1[k]);
                        }
                        int count1 = 0,count0 = 0;
                        for(int k = 0;k<r-l+1;k++)
                        {
                            if(prefix1[k]==maxi)   //找出所有最大值的k
                            {
                                count1+=(a[k]>>(17-j))&1;
                                count0+=(!((a[k]>>(17-j))&1));
                            }
                        }
                        int add = 0;
                        if(count0>count1) add=0; else add=1;
                        //更新prefix前缀
                        x_1+=(add<<(17-j));
                        for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(add^((a[k] >> (17-j))&1));
                    }
                    j++;
                }
            }
            
            
            
            int x_2 = x+(0<<(17-j));
            for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix[k]<<1)+(0^((a[k] >> (17-j))&1));
            j=++j_1;
            while(j<=17)
            {
                if(a_e[j]!=a_a[j]) 
                {
                    x_2+=(1<<(17-j));
                    for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(1^((a[k] >> (17-j))&1));
                }
                else
                {
                    if((r-l+1)-a_e[j]!=a_e[j])
                    {
                        x_2+=(0<<(17-j));
                        for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(0^((a[k] >> (17-j))&1));
                    }
                    else
                    {
                        int maxi = prefix1[0];
                        for(int k = 1;k<r-l+1;k++)
                        {
                            maxi = max(maxi,prefix1[k]);
                        }
                        int count1 = 0,count0 = 0;
                        for(int k = 0;k<r-l+1;k++)
                        {
                            if(prefix1[k]==maxi)   //找出所有最大值的k
                            {
                                count1+=(a[k]>>(17-j))&1;
                                count0+=(!((a[k]>>(17-j))&1));
                            }
                        }
                        int add = 0;
                        if(count0>count1) add=0; else add=1;
                        //更新prefix前缀
                        x_2+=(add<<(17-j));
                        for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(add^((a[k] >> (17-j))&1));
                    }
                    j++;
                }
            }

            bool flag = true;
            set<int> g;
            for(int  i  =0;i<r-l+1;i++)
            {
                if(g.find(a[i]^x_1)==g.end()&&(a[i]^x_1)>=l&&(a[i]^x_1)<=r) g.insert(a[i]^x_1);
                else{
                    flag=false;
                    break;
                }
            }
            if(flag) cout << x_1<<endl;
            else cout<<x_2<<endl;
        }
        else cout << x << endl;
    }
    system("pause");
}

官方解法(待更新)

  • 可能的x的空间:ai⊕l,∀ia_i \oplus l,\forall iail,i
  • 遍历空间中每个x。
    • aia_iai的构造,aia_iai是互不相同的。故对每个候选x,ai⊕xa_i\oplus xaix也是互不相同的。
    • 因此只要mini{ai⊕x}=l,maxi{ai⊕x}=rmin_i \{a_i\oplus x\} =l,max_i \{a_i\oplus x\} =rmini{aix}=l,maxi{aix}=r即可。这是字典树的应用(必要条件也是充分条件)
  • 结点最多个数:最底层r-l+1个。以上每一层<=r-l+1个结点。共32层。
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>
#include<math.h>
typedef long long ll;
using namespace std;
ll idx=0;

void insert(ll x,vector<vector<ll>> &nxt)
{
    ll now = 0 ;
    for(int i = 31;i>=0;i--)
    {
        int y = (x>>i)&1;
        if(!nxt[now][y]) nxt[now][y]=++idx; now=nxt[now][y];
    }
}

ll query_max(ll x,vector<vector<ll>> &nxt)
{
    ll now = 0,res=0;
    for(int i = 31;i>=0;i--)
    {
        int y = (x>>i)&1;
        if(nxt[now][y^1])
        {
            res+=(1<<i);
            now=nxt[now][y^1];
        }
        else now = nxt[now][y];
    }
    return res;
}


ll query_min(ll x,vector<vector<ll>> &nxt)
{
    ll now = 0,res = 0;
    for(int i = 31;i>=0;i--)  //到最后一层结点一定存在
    {
        int y = (x>>i)&1;
        if(nxt[now][y])    //相同为0
        {
            now = nxt[now][y];
        }
        else
        {
            now = nxt[now][y^1];
            res += (1 << i);
        }
    }
    return res;
}



int main()
{
    int t;
    cin>>t;
    for(int i = 0;i<t;i++)
    {
       ll l,r;
       cin>>l>>r;
       vector<ll> a;
       vector<vector<ll> > nxt((r-l+1)*32 , vector<ll>(2 , 0)) ;
       idx = 0;
       for(int k = 0;k<r-l+1;k++)
       {
           ll c;cin>>c;a.push_back(c);
           insert(c,nxt);
       }
       //枚举每一个x
       ll x;
       for(ll d : a)
       {
          x = d^l; 
          if(query_max(x,nxt)==r&&query_min(x,nxt)==l) break;
       }
       cout << x << endl;
    }
    system("pause");
}


E题:Gojou and Matrix Game(2500-GOOD)

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

  • 此题思维量较大。是很好的一道博弈题,还反映出对绝对值不等式的理解不够深入
  • 另:使用cin超时,换成scanf

思路

  • 设当前点坐标(i,j)。假设窗口外的点的输赢情况已确定。如果:

    • 最后一步在窗口外下的点如果全为输,则该点最后下必赢,否则必输。
  • 看似是一个先有鸡还是先有蛋的问题。实际上可以采用一定的顺序合理解决。

  • 按权值从大到小的顺序填写。令dp[i][j]=1dp[i][j]=1dp[i][j]=1当且仅当最后一步在(i,j)下会赢。权值最大点处显然有dp[i][j]=1dp[i][j]=1dp[i][j]=1.考察权值第二大的点,如果权值最大的点在其窗口内,那么对手只能采取在比这个权值第二大点小的点落子。以此类推。

  • dp[i][j]=1dp[i][j]=1dp[i][j]=1当且仅当∀i,,j,∈S,∣i−i,∣+∣j−j,∣≤k\forall i^,,j^,\in S,|i-i^,|+|j-j^,|\leq ki,,j,S,ii,+jj,k,其中S为按上述顺序已经确定的dp[i][j]=1dp[i][j]=1dp[i][j]=1的点集合。

  • 判断∀i,,j,∈S,∣i−i,∣+∣j−j,∣≤k\forall i^,,j^,\in S,|i-i^,|+|j-j^,|\leq ki,,j,S,ii,+jj,k是否成立,由绝对值不等式,等价于∣i−i,+j−j,∣≤k|i-i^, + j-j^,| \leq kii,+jj,k∣i−i,−j+j,∣≤k|i-i^, - j+j^,| \leq kii,j+j,k是否同时成立。(或分同号和异号讨论一下)只需存储8个数值:

    • j−ij-iji的最大值及其对应的j+ij+ij+i
    • j−ij-iji的最小值及其对应的j+ij+ij+i
    • j+ij+ij+i的最大值及其对应的j−ij-iji
    • j+ij+ij+i的最小值及其对应的j−ij-iji
  • 事实上,只需存储4个值。对应的所有数值可以全部的全去掉。

代码

#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>

using namespace std;
const int mod = 998244353;  
typedef long long ll;

int v[2005][2005];    //保存结果
pair<int,int> a[4000003];   //值到位置的映射



int main()
{
    int n,k;
    scanf("%d %d",&n,&k);
    for(int i = 1;i<=n;i++)
    {
        for(int j = 1;j<=n;j++) 
        {
            scanf("%d",&v[i][j]);
            a[v[i][j]].first=i;
            a[v[i][j]].second=j;
        }
    }
    int iplusjmin = a[n*n].first+a[n*n].second;
    int iplusjmax = a[n*n].first+a[n*n].second;
    int iminusjmin = a[n*n].second-a[n*n].first;
    int iminusjmax = a[n*n].second-a[n*n].first;
    v[a[n*n].first][a[n*n].second]=1;
    for(int p = n*n-1;p>=1;p--)
    {
        int i = a[p].first;
        int j = a[p].second;
        int a = i+j-iplusjmax;
        int b = i+j-iplusjmin;
        int c = i-j+iminusjmin;
        int d = i-j+iminusjmax;
        if(max(max(max(abs(a),abs(b)),abs(d)),abs(c))<=k)
        {
            v[i][j]=1;
            iplusjmin = min(i+j,iplusjmin);
            iplusjmax = max(i+j,iplusjmax);
            iminusjmin = min(j-i,iminusjmin);
            iminusjmax = max(j-i,iminusjmax);
        }
        else v[i][j]=0;
    }
    for(int i =1;i<=n;i++)
    {
        for(int j = 1;j<=n;j++)
        {
            if(v[i][j]) printf("M");
            else printf("G");
        }
        printf("\n");
    }
    system("pause");
}

F-Juju and Binary String(2700-GOOD)重新解读式子的含义在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

思路

  • 利用必要条件缩小搜索空间:猜测只可能拆成很少的几块,然后讨论剩下的
  • 是否存在很好判断。不说了。
  • 目标中1的个数
    x=m⋅#onesnx = \frac{m \cdot \# ones}{n}x=nm#ones

上述式子什么含义?

  • 将原始字符串首尾相连,从任意点开始所有长度为m的滑动窗口中1的个数的均值!
    • 注意到原始字符串中1个1在m个窗口中出现。
  • 对所有滑动窗口1的个数都不能同时大于均值,或同时小于均值。同时,由于相邻滑动窗口中1的个数至多相差1.因此一定存在某个滑动窗口中1的个数取到均值x.

代码

#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>

using namespace std;
const int mod = 998244353;  
typedef long long ll;





int main()
{
    int t;
    cin >> t;
    for(int l = 0;l<t;l++)
    {
        int n,m;
        cin >> n >> m;
        string s; cin >> s;
        int count1=0;
        for(int k = 0;k<s.size();k++) {count1+=(s[k]-'0');}
        if((ll)count1*(ll)m%(ll)n!=0)cout<<-1<<endl;
        else    //一定存在
        {
            count1=(int)((ll)count1*(ll)m/(ll)n);
            //从首个开始的滑动窗口中1的个数
            int c_1=0;
            for(int i =0;i<m;i++) c_1+=(s[i]-'0');
            if(c_1==count1)
            {
                cout<<1<<endl;
                cout<<1<<" "<<m<<endl;
            }
            else
            {   
                for(int i = 1;i<n;i++)
                {
                    c_1-=(s[i-1]-'0');
                    c_1+=(s[(i+m-1)%n]-'0');
                    
                    if(c_1==count1)
                    {
                        if(i+m-1<n)
                        {
                            cout<<1<<endl;
                            cout<<i+1<<" "<<i+m<<endl;
                        }
                        else
                        {
                            cout<<2<<endl;
                            cout<<1<<" "<<(i+m-1)%n+1<<endl;
                            cout<<i+1<<" "<<n<<endl;
                        }
                        break;
                    }
                        
                    
                }
            }
        }
    }
    system("pause");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值