Codeforces Round #779 (Div. 2) 记录

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 = g c d ( 1 ⋅ p 1 , 2 ⋅ p 2 ⋯ n ⋅ p n ) ≤ 2 g = gcd(1 \cdot p_1,2\cdot p_2 \cdots n \cdot p_n) \leq 2 g=gcd(1p1,2p2npn)2(GOOD)

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

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

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

思路

  • 寻找特殊值:最大值排在第一个对应的power必为1,其余的都>1;等价地可以从c为1开始的值开始构建排列
  • 考察两个邻接的c。必要条件 c i + 1 − c i ≤ 1 c_{i+1} - c_i \leq 1 ci+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的空间: a i ⊕ l , ∀ i a_i \oplus l,\forall i ail,i
  • 遍历空间中每个x。
    • a i a_i ai的构造, a i a_i ai是互不相同的。故对每个候选x, a i ⊕ x a_i\oplus x aix也是互不相同的。
    • 因此只要 m i n i { a i ⊕ x } = l , m a x i { a i ⊕ x } = r min_i \{a_i\oplus x\} =l,max_i \{a_i\oplus x\} =r mini{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)。假设窗口外的点的输赢情况已确定。如果:

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

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

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

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

    • j − i j-i ji的最大值及其对应的 j + i j+i j+i
    • j − i j-i ji的最小值及其对应的 j + i j+i j+i
    • j + i j+i j+i的最大值及其对应的 j − i j-i ji
    • j + i j+i j+i的最小值及其对应的 j − i j-i ji
  • 事实上,只需存储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 ⋅ # o n e s n x = \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、付费专栏及课程。

余额充值