牛客网暑期ACM多校训练营(第七场)- (A,C,E,J)

比赛链接https://www.nowcoder.com/acm/contest/145#question

A Minimum Cost Perfect Matching

题意:求出0到n-1的一个排列p[i] , 使得所有(i&p[i])之和最小 , &表示按位与。

解析:由于对于每一位值为‘1’的情况的个数小于此位值为‘0’的情况的个数,那么我们总可以找到总和为0的方案。

  • 写出二进制发现,对于每个k的2^k,将2^k - 1与2^k对称匹配即两两按位与为0,比如0~9的二进制:
  • 0000,0001,0010,0011,0100,0101,0110,0111,1000,1001。
  • 我们从后往前匹配,先匹配2^3~9,即使得1000与0111匹配、1001与0110匹配。
  • 此时0110往后已经匹配完成,然后我们只需从5往前匹配即可,即匹配2^2~5,即0100与0011、0101与0010匹配。
  • 再匹配0000,0001即可。
  • 对于其他情况也是类似,匹配0~n时,找到k为<=n最大的2的次幂,它的左右两边匹配,然后递归即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int MAXN=5*1e5+5;

int n,pow2[30],ans[MAXN];

void init()
{
    pow2[0]=1;
    for(int i=1;i<30;i++)
        pow2[i]=pow2[i-1]*2;
    return;
}

int Find(int x)
{
    for(int i=0;i<=28;i++)
        if(pow2[i]<=x&&pow2[i+1]>x) return i;
    return 0;
}

void solve(int x)
{
    if(x<0) return;
    if(x==0)
    {
        ans[0]=0;
        return;
    }
    if(x==1)
    {
        ans[0]=1;
        ans[1]=0;
        return;
    }
    int it=Find(x);
    int num=pow2[it]-1;
    for(int i=pow2[it];i<=x;i++)
    {
        ans[i]=num;
        ans[num]=i;
        num--;
    }
    int res=num;
    solve(res);
}
int main()
{
    init();
    scanf("%d",&n);
    solve(n-1);
    for(int i=0;i<n;i++)
        printf("%d ",ans[i]);
    return 0;
}
C Bit Compression

题意:初始长度2^n的01序列,有n次操作,每次操作要从^,&,|中选择1个运算符,从前往后将相邻两个元素用选择的运算符计算,计算后的值代替原来的两个元素,那么每次操作可使得01序列长度减半,n次操作后01序列长度就变成了1,问n次操作后使得剩余字符为1的方案数。

解析:直接暴力复杂度是3^n,我们将最后剩余4位以下时每种可能的01序列的合法方案求出来,那么可以将复杂度降低至3^(n-4)。而且此题直接暴力也可过。

代码(标程562ms)

//标程
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
 
using namespace std;
using namespace __gnu_pbds;
 
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define fbo find_by_order
#define ook order_of_key
 
typedef long long ll;
typedef pair<ll,ll> ii;
typedef vector<int> vi;
typedef long double ld;
typedef tree<ll, null_type, less<ll>, rb_tree_tag, tree_order_statistics_node_update> pbds;
typedef set<ll>::iterator sit;
typedef map<ll,ll>::iterator mit;
 
bool B[22][(1<<20)+5];//b[i][j]是第i次操作位置j的值
int ans = 0;
int dp[5][(1<<16)+15];//dp[i][j]是后面i位的二进制和为j的情况
 
void gen(int n)
{
    int pow3=1;
    for(int i=0;i<n;i++) pow3*=3;
    for(int i=0;i<(1<<(1<<n));i++)//有位置(1<<n)个,每个位置取0或1,总方案数为(1<<(1<<n))
    {
        for(int k=0;k<(1<<n);k++) //生成第i种方案
        {
            if(i&(1<<k)) B[n][k] = 1;
            else B[n][k] = 0;
        }
        for(int j=0;j<pow3;j++)   //会有pow3种运算符选择方法(这里相当于三进制枚举?)
        {
            int cur = j;
            for(int k=n-1;k>=0;k--)//有n次操作
            {
                int z=cur%3;
                for(int l=0;l<(1<<k);l++)//合并相邻两项
                {
                    if(z==0) B[k][l]=B[k+1][2*l]&B[k+1][2*l+1];
                    else if(z==1) B[k][l]=B[k+1][2*l]^B[k+1][2*l+1];
                    else B[k][l]=B[k+1][2*l]|B[k+1][2*l+1];
                }
                cur/=3;
            }
            dp[n][i] += B[0][0];
        }
    }
    return;
}
 
void solve(int n)
{
    if(n<=4)
    {
        int bit = 0;
        for(int i=0;i<(1<<n);i++)
        {
            if(B[n][i]) bit^=(1<<i);
        }
        ans += dp[n][bit];//直接加上剩余的n次操作的方案数
        return ;
    }
    for(int i = 0; i < 3; i++)//3种运算符
    {
        for(int j = 0; j < (1<<(n-1)); j++)
        {
            if(i==0) B[n-1][j] = B[n][2*j]&B[n][2*j+1];
            else if(i==1) B[n-1][j] = B[n][2*j]^B[n][2*j+1];
            else B[n-1][j] = B[n][2*j]|B[n][2*j+1];
        }
        solve(n-1);
    }
}
 
int main()
{
    ios_base::sync_with_stdio(0); cin.tie(0);
    int n; cin>>n; string z; cin>>z;
    for(int i=0;i<=4;i++) gen(i);
    for(int i=0;i<(1<<n);i++)
    {
        B[n][i] = z[i] - '0';
    }
    solve(n);
    cout<<ans<<'\n';
}

直接暴力(代码来自https://www.nowcoder.com/acm/contest/view-submission?submissionId=31303133):

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define bug(x)  cout<<"@@  "<<x<<"\n"
#define rep(i, l, r) for(int i = l; i < r; i++)
#define per(i, r, l) for(int i = r; i >= l; i--)
  
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
 
const int N = (int) 1e5 + 11;
const int M = (int) 1e6 + 11;
const int INF = (int)0x3f3f3f3f;
const int MOD = (int)1e9 + 7;
 
#include <ext/rope>
using namespace __gnu_cxx;
 
unordered_map<string, int>mp[22];
int main(){
      
    int n; string s, t; cin>> n>> s;
    mp[n][s] = 1;
    per(i, n - 1, 0){
        for(auto j : mp[i + 1]){
            s = j.first; int cur = j.second, len = s.size() >> 1;
        //  cout<<"##"<<s<<"\n";
            t = "";
            rep(I, 0, len) t += '0' + ((s[I << 1] - '0') & (s[I << 1 | 1] - '0'));      
            mp[i][t] += cur;
 
            t = "";
            rep(I, 0, len) t += '0' + ((s[I << 1] - '0') | (s[I << 1 | 1] - '0'));
            mp[i][t] += cur;
 
            t = "";
            rep(I, 0, len) t += '0' + ((s[I << 1] - '0') ^ (s[I << 1 | 1] - '0'));
            mp[i][t] += cur;
        }
    }
    cout<<mp[0]["1"]<<"\n";
    return 0;
}
J Sudoku Subrectangles

题意:有n*m的由大小写字母组成的矩阵,Sudoku Subrectangles是指一个子矩形满足:任意一行的字母互不相同;任意一列的字母互不相同。求矩阵的Sudoku Subrectangles数量。

解析:枚举行的上下边界,复杂度n*52,然后枚举每一行处理,复杂度n*52*m,具体见代码。

代码(来自https://www.nowcoder.com/acm/contest/view-submission?submissionId=31212446

//https://www.nowcoder.com/acm/contest/view-submission?submissionId=31212446
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
typedef long long li;

const int maxn = 1000;
int n, m;
char s[maxn][maxn + 1];
li appear[maxn];
int argue[maxn];
int last[52];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i)
    {
        scanf("%s", s[i]);
        for (int j = 0; j < m; ++j)
        {
            if (islower(s[i][j]))
            {
                s[i][j] -= 'a';
            }else{
                s[i][j] -= 'A' - 26;
            }
        }
    }
    li ans = 0;
    for(int xl = 0; xl < n; ++xl)//行上限
    {
        fill(appear, appear + m, 0);//标记列的字符情况
        fill(argue, argue + m, -1);
        for (int xr = xl; xr < n; ++xr)//行下限
        {
            if (xr - xl + 1 > 52) break;
            for (int i = 0; i < m; ++i)//列
            {
                int x = s[xr][i];
                if (appear[i] >> x & 1)//x重复出现过
                {
                    argue[i] = i;      //第i列是出现过重复的
                }
                appear[i] |= 1LL << x;
            }
            fill(last, last + 52, -1);//last[i]记录i最后出现位置
            for (int i = 0; i < m; ++i)
            {
                //如果argue[i]被之前赋值,有argue[i]=i,也就是第i列无论如何不能选,
                //如果argue[i]被之前没有赋值,看上一次出现位置
                argue[i] = max(argue[i], last[s[xr][i]]);
                last[s[xr][i]] = i;
            }

            for (int l = -1, r = 0; r < m; ++r)
            {
                l = max(l, argue[r]);
                ans += r - l;
            }
        }
    }
    printf("%lld\n", ans);
    return 0;
}
E Counting 4-Cliques

题意:构造一个<=75个点的图,使得大小为4的团恰有k个。 k<=1e6

解析:构造一个大小为t的完全图,和额外的x1, x2, x3, x4, x5五个点。 t的值满足:最大的t完全图满足其中的k4个数小于等于k,此完全图中的k4数量加上另外5个点连在此完全图中增加的k4数量正好为k。

 x1, x2, x3, x4, x5五个点之间没有边,他们只会向t完全图中的t个点连边。x1, x2, x3, x4, x5分别连了a,b,c,d,e条边,新构成C(a, 3)+C(b, 3)+C(c, 3)+C(d, 3)+C(e, 3)个大小为4个k4。找到最大的t,枚举a,b,c,d,计算e直到满足够k个k4(一定能找到,但证明不清楚。。。)。

别人题解https://blog.csdn.net/hhhhhhxh/article/details/81543926:(侵删)

  • 首先考虑构造完全图,那么t个点的完全图一共能有C(t, 4)个大小为4的团。
  • 但是在C(t, 4)和C(t+1, 4)之间会有空缺,因此在完全图外放若干个点,每个点与这个完全图中的若干个点连边。
  • 最后会形成类似于C(t, 4)+C(x1, 3)+C(x2, 3)+…这样的式子,并且要能补满中间的空隙。

代码来自https://blog.csdn.net/hhhhhhxh/article/details/81543926

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
typedef long long ll;

ll C4(ll n) { // C(n, 4)
    ll ans = n * (n - 1) * (n - 2) * (n - 3) / (1 * 2 * 3 * 4);
    return ans;
}

ll C3(ll n) { // C(n, 3)
    ll ans = n * (n - 1) * (n - 2) / (1 * 2 * 3);
    return ans;
}

ll mp[200000 + 5];
int k;

int main()
{
    memset(mp, -1, sizeof(mp));
    for (ll i = 1; i <= 100; i++) {
        mp[C3(i)] = i;
    }
    scanf("%d", &k);
    ll C = 70;
    ll t = 4;
    while ((t + 1) <= C && C4(t + 1) <= k) t++;//最大的t完全图满足其中的k4个数小于等于k

    C = min(C, t);//C个点构成一个完全图,其中的k4数量加上另外5个点连在此图中增加的k4数量正好为k
    ll a, b, c, d, e = -1;
    for (a = 2; a <= C; a++) {
        for (b = a; b <= C; b++) {
            for (c = b; c <= C; c++) {
                for (d = c; d <= C; d++) {
                    ll cnt = C3(a) + C3(b) + C3(c) + C3(d);
                    if (cnt <= k - C4(t)//还没连满
                            && mp[k - C4(t) - cnt] >= 0
                            && mp[k - C4(t) - cnt] <= C) {
                        e = mp[k - C4(t) - cnt];
                        break;
                    }
                }
                if (e != -1)    break;
            }
            if (e != -1)    break;
        }
        if (e != -1)    break;
    }
    printf("%lld %lld\n", t + 5, t * (t - 1) / 2 + a + b + c + d + e);
    for (int i = 1; i <= t; i++) {
        for (int j = i + 1; j <= t; j++) {
            printf("%d %d\n", i, j);
        }
    }
    for (int j = 1; j <= a; j++)    printf("%lld %d\n", t + 1, j);
    for (int j = 1; j <= b; j++)    printf("%lld %d\n", t + 2, j);
    for (int j = 1; j <= c; j++)    printf("%lld %d\n", t + 3, j);
    for (int j = 1; j <= d; j++)    printf("%lld %d\n", t + 4, j);
    for (int j = 1; j <= e; j++)    printf("%lld %d\n", t + 5, j);
}

猜你喜欢

转载自blog.csdn.net/sdau20163942/article/details/81543764