Codeforces Round #664 (Div. 2&&Div. 1)Div2的A-E题解

Codeforces Round #664 (Div. 2&&Div. 1)Div2的A-E题解
//写于大号rating值2075/2184,小号rating值1887/1887
//第一次爆零场,而且是在赛后两道题目都被系统X掉
//大号打的div1组,rating值-109
//这两天离校回家,在亲戚家作客,有可能会鸽一两场比赛,如果鸽了题解也会在赛后补题照常写

//沮丧肯定是有的,但是确实是自己有问题,A题(对应Div2的D)理解错了题意一个细节导致代码出错,然而居然还能把pretest过掉…B题(对应Div2的E)对复杂度的分析太差,也确实是自己思路太狭隘了,完全没往哈希上想,以为是个dfs剪枝,强行剪过了pretest然后重测tle…
//被教育了,但是也让自己看到了缺陷,而且现在的我也确实配不上黄名。
//也不必失去信心什么的,A-C题仍然一如既往的“光速”过题,对比半年多前那个俯冲1300的自己确实已经进步了很多很多很多。
//明天会更好,ACMER从来都不会被失败和挫折击溃

//赛后补了div2的A-E题,照例写个题解

比赛链接:https://codeforces.com/contest/1395

A题
简单思维

这道题需要抓住一些本质特点,关于奇偶性

首先我们分析一下对于一个回文串来说,构成这个回文串的字母数量有什么特点。
1.当回文串总长度为偶数时,由于任意一个位置的字母,在回文串中都必定会有另一个不同位置的字母与它相同,因此这个回文串中出现的任意一种字母,它的出现次数都是偶数
2.当回文串总长度为偶数时,位于中间的那个字母是特殊位置,它与自身对应,因此它的总出现次数为奇数。也就是说这种情况下,除了中间位置的字母外,所有字母的出现次数都是偶数,或者换个说法,出现次数为奇数的字母有且只有一个。

综上,可以构成回文串的情况下,我们手上每个字母的出现次数,要么全部都是偶数,要么有且只有一个是奇数。

再看现在题目给我们四种字母r,g,b,w。我们可以统计一下这四种出现次数中是偶数的有几个,记为tot。

当tot等于0或者1的时候,已经满足了我们上面推出的构成回文串条件,直接输出Yes
当tot大于1时,我们需要判断一下,能否通过题目给定的操作方式将tot改变为0或1。

题目给定的操作是,每次可以把1个r和1个g和1个b变为3个w。
我们注意到,对于四个字母的改变数量都是奇数,也就是说每次操作后,原来出现次数是奇数的会变成偶数,原来出现次数是偶数的会变成奇数。

tot为2时,也就是奇数个数为2,偶数个数为2,经过操作后奇数个数仍然为2,偶数个数仍然为2,因此此时直接输出No
tot为3时,也就是奇数个数为3,偶数个数为2,经过操作后奇数个数为1,偶数个数为3,满足要求;
tot为4时,也就是奇数个数为4,偶数个数为0,经过操作后奇数个数为0,偶数个数为4,满足要求。但是此时我们需要检测下,rgb三个字母的个数是不是都不为0,只有这样我们才有操作的机会。如果可以操作,输出Yes,无法操作则输出No

讨论完毕

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        ll a,b,c,d;
        cin>>a>>b>>c>>d;
        int tot=0;
        if(a&1) tot++;
        if(b&1) tot++;
        if(c&1) tot++;
        if(d&1) tot++;
        if(tot==0||tot==1) cout<<"Yes"<<endl;
        else
        {
    
    
            if(tot>2&&a&&b&&c) cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
}

B题
简单构造

题意为在一个n × \times ×m的棋盘上,一开始在(x,y)的位置(初始位置必定不在边界位置)上放了一个车(只能横着走或者竖着走),现在希望你输出一种行走路径,能把棋盘上的每个点都走一遍。(注意每次移动只有终点是作为被走到的带你,路上中间的点是不算的)

先想一个起点在(1,1)也就是左上角的可以走遍整个棋盘的方案,一开始往右走,走到头后向下走1格后向左走,走到头后向下走1格后向右走…不断循环,也就是走个蛇形路线。

那么我们先把棋子从初始位置(x,y)向上移到(1,y)的位置再移动到(1,1),之后再按照上述路线走即可,碰到(1,y)和(x,y)两个格子就越过他们走,由于初始位置不在边界上,也就是说y不等于1也不等于m,我们跳过这两个位置是必定可行的。(注意第一步不要向左移动到(x,1),左右边界位置是我们这种方案下,改变行走方向的点,会造成我们路径难以构造)
接下来代码模拟完成这个过程就是了

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    
    
    IOS;
    int n,m,x,y;
    cin>>n>>m>>x>>y;
    cout<<x<<' '<<y<<endl;//初始位置
    cout<<1<<' '<<y<<endl;//移动到最上面
    for(int i=1;i<=n;i++)//从(1,1)向右,开始走蛇形路线
    {
    
    
        if(i&1)//奇数行向右走
        {
    
    
            for(int j=1;j<=m;j++)
            {
    
    
                if(j==y&&(i==1||i==x)) continue;//碰到(1,y)和(x,y)这两个点就跳过
                cout<<i<<' '<<j<<endl;
            }
        }
        else//偶数行向左走
        {
    
    
            for(int j=m;j>=1;j--)
            {
    
    
                if(j==y&&(i==1||i==x)) continue;
                cout<<i<<' '<<j<<endl;
            }
        }
    }
}

C题
位运算,贪心,暴力

题意为给你两个长度分别为n和m的数组a和数组b,对于数组a中的每一个数,你都需要在数组b中找到一个数与之进行&运算,作为c数组的值。(对于不同的a数组中的数,你可以选择同一个b数组中的数进行运算)
现在你希望构造出的长度为n的c数组中,所有数字进行|运算后得到的值最小,求出这个最小值。

首先我们要总结一下|(或)运算的特点,或运算,对于同一个二进制位上,任意一个数字对应的二进制数在该位上出现了1,那么最后的结果在这个位上必然有1。
进行贪心的策略,对于最高位的1来说,我们必然希望它优先被全部清除掉。
我们直接暴力for循环检测,所有a数组中,当前数字在最高位上是否为1,如果是1又是否可以通过与b数组的数进行&运算将这个1除去。如果所有a数组中的数,都可以通过进行&运算将这个1除去,那么代表最后的结果是可以不包括这个最高位的0的。
对于更低位的1来说,如果前面的更高位已经有1可以被全部除去了,那么它的选择就必须在那些可以除去这些最高位的基础上进行,我们用一个del数组记录哪些更高位的1已经可以被全部删除,在暴力for循环检测过程中,在加一个检测是否对del数组中的每一位都不存在1即可。

最后的答案为所有位上都为1的初始值,也就是(1<<9)-1,再减去del数组中可以被除去的所有位上的1后的值。

题目条件中,数字最高只有9位二进制,复杂度为O(9 × \times × 9 × \times × n × \times × m)撑死了才3e7级别的复杂度。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

vector<int>del;//记录当前为止,哪些更高位的1可以被删除,对于更低位的1来说,在&运算删除自身位上1的过程中还需满足同时删除所有del中更高位的1

int32_t main()
{
    
    
    IOS;
    int n,m;
    cin>>n>>m;
    vector<int>a(n),b(m);
    for(auto &x:a) cin>>x;
    for(auto &x:b) cin>>x;
    for(int i=8;i>=0;i--)
    {
    
    
        int flag=1;//记录第i位的1是否可全部通过&运算除去
        for(int j=0;j<n;j++)
        {
    
    
            if(a[j]&(1<<i))//如果a[j]的第i位上出现了1,检测是否可以通过&运算将这个1除去
            {
    
    
                bool F=0;//记录a[j]&b[]是否存在一个组合满足不存在更高的已经确定可删除的位以及当前位上出现1
                for(int k=0;k<m;k++)
                {
    
    
                    int temp=a[j]&b[k];
                    bool f=1;//记录a[j]&b[k]的值是否满足不存在更高的已经确定可删除的位以及当前位上出现1
                    for(auto x:del) if(temp&(1<<x)) f=0;
                    if(temp&(1<<i)) f=0;
                    if(f) F=1;//出现任意一个满足要求的组合,F置为1
                }
                if(!F)
                {
    
    
                    flag=0;//如果任意一个a[]无法将第i位上的1除去,那么flag就为0
                    break;
                }
            }
        }
        if(flag) del.push_back(i);//如果所有第i位的1都可在满足删除更高位的必须删除的1的基础上,同时可删除第i位的1
        //代表第i位的1也可被除去,压入del数组中
    }
    int ans=(1<<9)-1;
    for(auto x:del) ans-=(1<<x);
    cout<<ans<<endl;
}

D题
贪心,暴力,前缀和

给定n个数字,你需要找到一种排列,在满足题目要求的条件下,最后的总和最大。
题目要求,从数列的头部扫到尾部的过程中,如过某一个值大于给定的值m,那么这个数字后的连续d个数字不被计入到最后的总和中。

//比赛的时候将,当d=1,m=2时
//数列3 3 4的结果,理解成为了3
//然而实际结果应该是7
//不知道我是怎么把pretest骗过去的…

首先对于大于m的所有数来说,如果我们希望得到其中的某一个数值,那么对应的他后面的d个数字就不能计入最后的结果中。也就是说每d+1个数字,我们能使一个大于m的数被取到(当然这不见得是最优的构造)。
如果我们手上有x个数字,记x/(d+1)为cas,如果x%(d+1)等于0的话,那么这x数字最多可以使得cas个大于m的数被选择,如果x%(d+1)不为0的话,最后的末尾部分我们可以再增添一组,最多可以使得cas+1个大于m的数被选择。

对于小于等于m的数字来说,他们只要不出现在大于m的数字后d个内,他们就必然会被取到,我们可以通过枚举多少个小于等于m的数字被取到(这些数字当然是小于等于m的数中最大的,放在数列最前面),剩下的数字作为构造大于m的数字,和大于m的数字一同进行上面一段的操作。在大于m的数字当中选定的那些数字自然也是最大的。

因此我们把数字按照大于m和小于等于m分为两类,从大到小排序后求一个前缀和,就可以O(1)查询前i个最大的数的和是多少,暴力枚举小于等于m的数字我们要取到的个数即可。

其他的讨论方法或多或少都会有难以思考的点或者根本讨论不出来。只有这个方法是最简洁的,这个思路实际上在半年前的某一道题中也出现过一次。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

ll n,d,m;
deque<ll>high,low;

bool cmp(ll a,ll b)
{
    
    
    return a>b;
}

int32_t main()
{
    
    
    IOS;
    cin>>n>>d>>m;
    for(ll i=0;i<n;i++)
    {
    
    
        ll x;
        cin>>x;
        if(x>m) high.push_back(x);
        else low.push_back(x);
    }
    d++;
    sort(high.begin(),high.end(),cmp);
    sort(low.begin(),low.end(),cmp);
    high.push_front(0);
    low.push_front(0);
    ll ans=0;
    for(ll i=1;i<high.size();i++)
        high[i]+=high[i-1];
    for(ll i=1;i<low.size();i++)
        low[i]+=low[i-1];
    for(ll i=0;i<low.size();i++)//i为小于等于m的数字中我们取的个数
    {
    
    
        ll j=(n-i)/d;//j为大于m的数字中我们取的个数
        if((n-i)%d) j++;
        j=min((ll)high.size()-1,j);
        ans=max(ans,high[j]+low[i]);
    }
    cout<<ans<<endl;
}

E题
暴力dfs,哈希优化检测

都看到E了应该没必要讲的和上面一样详细了(肚子也饿了想快点吃饭去了)。
首先第一个关键结论,我们最后构造的结果满足题目要求的话,必须是每个点都恰好有一条入边。
之后再注意到k的值特别小,我们可以dfs暴力枚举k,接着就是一个check最后结果的问题了。

比赛的时候我直接用bool数组模拟…剪枝过掉了pretest,tle在了赛后重测…这里要用hash来优化这个最后的check,对每次枚举做到O(1)的check复杂度。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=2e5+7;

struct Node
{
    
    
    ll to,weight;
};

vector<Node>field[maxn];
vector<ll>sum[10];
vector<ll>hash1[10],hash2[10];//hash[i][j]代表对于出度为i的点,我们选择第j小的边的时候,对应hash函数的增加值,用于dfs过程中O(1)计算
ll n,m,k;
ll ans=0;
ll tar1,tar2;//两个hash函数的最终结果,tar1为累加1到n的i三次方结果,tar2位累加1到n的i二次方结果

bool cmp(Node a,Node b)
{
    
    
    return a.weight<b.weight;
}

void dfs(ll deep,ll tot1,ll tot2)//tot1为第一个hash函数的统计结果,tot2为第二个hash函数的统计结果
{
    
    
    if(deep==k+1)
    {
    
    
        if(tot1==tar1&&tot2==tar2) ans++;//两个hash函数check结果都满足,判定所有点都被走到过了
        return ;
    }
    for(ll i=0;i<deep;i++)
    {
    
    
        dfs(deep+1,tot1+hash1[deep][i],tot2+hash2[deep][i]);//这里用hash优化了......
    }
}

int32_t main()
{
    
    
    IOS;
    cin>>n>>m>>k;
    for(ll i=0;i<m;i++)
    {
    
    
        ll u,v,w;
        cin>>u>>v>>w;
        field[u].push_back({
    
    v,w});
    }
    for(ll i=1;i<=n;i++)
    {
    
    
        tar1+=i*i*i;
        tar2+=i*i;
        sort(field[i].begin(),field[i].end(),cmp);
        sum[field[i].size()].push_back(i);
    }
    for(ll i=1;i<=k;i++)
    {
    
    
        for(ll j=0;j<i;j++)
        {
    
    
            unordered_map<ll,ll>M;
            ll temp1=0,temp2=0;
            for(auto &x:sum[i])
            {
    
    
                ll to=field[x][j].to;
                if(M.find(to)==M.end())
                {
    
    
                    temp1+=to*to*to;
                    temp2+=to*to;
                }
                M[to]=1;
            }
            hash1[i].push_back(temp1);
            hash2[i].push_back(temp2);
        }
    }
    dfs(1,0,0);
    cout<<ans<<endl;
}

猜你喜欢

转载自blog.csdn.net/StandNotAlone/article/details/108006155