Codeforces Round #612 (Div. 2) A~E2

A. Angry Students

A

Angry Students

题意:一排学生打雪仗,A是生气的人,P是文静的人,每回合A会影响后面的一个P使他也生气,如PAPP一回合之后是PAAP,求第几回合之后生气人数不再增加

题解:语法题,统计A后面连续的P的最大值即可

时间复杂度:O(n)

B. Hyperset

B

Hyperset

题意:给定n,m,n张牌每次选三张,m种属性对应一个游戏规则,在三种属性:‘S’,‘E’,‘T’,要么三种牌都相同,要么三张牌都不相同,求选择的方案数

题解:注意数据量,1500允许n^2算法,题目就简单了,三张牌循环两张,借用规则求出应该有的第三张,在所有牌中查询,注意最后(1)牌相同(2)可能性是轮换的要除以3。

时间复杂度:两重循环n^2,map查询logn,总时间复杂度O(k*n^2*logn)

#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
const int N = 100100;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef pair<int,int> PII;
typedef long long ll;

int n,m,d;
string s[10010];
map<string,int> mp;
int main(void){
    cin>>n>>m;
    for(int i = 1;i <= n;i++){
        cin>>s[i];
        mp[s[i]]++;
    }
    int ans = 0;
    for(int i = 1;i < n;i++){
        for(int j = i+1;j <= n;j++){
            string news;
            for(int k = 0;k < m;k++){
                if(s[i][k] == 'S' && s[j][k] == 'E')news += 'T';
                else if(s[i][k] == 'S' && s[j][k] == 'T')news += 'E';
                else if(s[i][k] == 'E' && s[j][k] == 'S')news += 'T';
                else if(s[i][k] == 'E' && s[j][k] == 'T')news += 'S';
                else if(s[i][k] == 'T' && s[j][k] == 'S')news += 'E';
                else if(s[i][k] == 'T' && s[j][k] == 'E')news += 'S';
                else if(s[i][k] == s[j][k])news += s[i][k];
            }
            if(news == s[i] && news == s[j])ans += (mp[s[i]]-2);
            else if((news == s[i] && news != s[j]) || (news != s[i] && news == s[j]))
                 ans += (mp[s[i]]-1);
            else ans += mp[news];
        }
    }
    cout<<ans/3<<endl;
    return 0;
}

C. Garland

C

Garland

题意:在一个排列中缺少一些数,用0表示空位,你可以填缺失的数,最后的权值计算规则是从头遍历,每改变一次奇偶性权值最小,求填补后最小权值

题解:注意到数据量100,最高允许O(n^3),考虑贪心,每次对已经存在的奇数偶数前面填写奇偶性相同的数字,但是会有问题,比如:剩余2个奇数时  2 0 0 0 3 0 0 5 0 0 0 7,显然将两个奇数填写在中间是最好的,于是用更全面的做法:DP[pos][odd][even][2],pos表示位置,odd表示剩余奇数元素数,even表示剩余偶数元素数,1表示前一个数状态是奇数,0是偶数(从前一个状态进行状态转移)

#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
const int N = 100100;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef pair<int,int> PII;
typedef long long ll;

int n,m,d;
int dp[101][101][101][2];//pos,剩余oddnum,剩余evennum,上一位状态
int q[110];

int main(void){
    cin>>n;
    int oddnum = n+1>>1;
    int evenum = n>>1;
    for(int i = 1;i <= n;i++){
        cin>>q[i];
        if(q[i]){
            if(q[i]&1)oddnum--;
            else evenum--;
        }
    }
    memset(dp,0x3f,sizeof dp);
    dp[0][oddnum][evenum][0] = dp[0][oddnum][evenum][1] = 0;
    for(int i = 1;i <= n;i++){
        for(int j = 0;j <= oddnum;j++){///奇数
            for(int k = 0;k <= evenum;k++){
                if(!q[i]){///1是上一位奇数,0是上一位偶数
                    dp[i][j][k][0] = min(dp[i-1][j][k+1][0],dp[i-1][j][k+1][1]+1);
                    dp[i][j][k][1] = min(dp[i-1][j+1][k][0]+1,dp[i-1][j+1][k][1]);
                }
                else{
                    if(q[i]&1){
                        dp[i][j][k][1] = min(dp[i-1][j][k][0]+1,dp[i-1][j][k][1]);
                    }
                    else{
                        dp[i][j][k][0] = min(dp[i-1][j][k][0],dp[i-1][j][k][1]+1);
                    }
                }
            }
        }
    }
    cout<<min(dp[n][0][0][0],dp[n][0][0][1]);
    return 0;
}

D. Numbers on Tree

D

Numbers on Tre​​​​​​e

题意:给定n个点的树和n个点的关系,下面n行代表关系,每个关系给出两个数字,前一个是该行的点的父亲节点是谁,后一个是子树中点权小于该点的点的数量,根据前一个可以知道树的形状,后一个可以知道树的子树的点权关系,现在树的每个节点权未知,问是否可以复原该树,可以按照点的顺序给出点权方案

题解:显然可以从子节点向上看,对整棵树进行填数,数据规模2000,支持O(n^2)算法,显然有解的话,可以所有点的排列(这一点请理解)最开始的想法是将树根据点权c[x],每次从一个集合中选取第c[x]+1的元素,如果点权是0,就选最小元素。但是WA16,不太理解,应该是点权是0的地方出现问题。所以改变思路,对于每个节点,子树填数,比如该节点子树有10个点,c[x]是5(有五个节点比这个节点的权小),那么根据bfs回溯,先把子节点确定,返回前四个整合好的vector,然后该点因为c[x]是5,选vector前5个较小的,添加自己的位置,再把后面的位置填数,比如:

(?)c[?]=5

5 2 3 1 4 x x x x x

前五个确定好位置,然后根节点存编号进vector中,后面依次把位置7,8,9,10,11的编号填进去

(注意理解,返回的vector存的是每个点的编号,而位置才是真正的数,利用了vector增长时候新增加的点会是所有位置+1的性质)怎么判断错误?一个点的子树返回的vector不满足c[i],比如c[i]是100,子节点只有50个点,要比100个点大,显然不成立,exit(0)

tips.这题学会了用vector存树,比用链式前向星方便说实话

#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int N = 100100;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef pair<int,int> PII;
typedef long long ll;

vector<int> e[N];
int n,c[N],p[N],a[N];

vector<int> dfs(int u){
    vector<int> ans;
    for(int i = 0;i < e[u].size();i++){
        vector<int> tmp = dfs(e[u][i]);
        for(int j = 0;j < tmp.size();j++){
            ans.push_back(tmp[j]);
        }
    }
    if(u == 0) return ans;
    if(ans.size() < c[u]){
        cout<<"NO\n";
        exit(0);
    }
    vector<int> newans;
    for(int i = 0;i < c[u];i++) newans.push_back(ans[i]);
    newans.push_back(u);
    for(int i = c[u];i < ans.size();i++) newans.push_back(ans[i]);
    swap(ans,newans);
    return ans;
}

int main(void){
    cin>>n;
    for(int i = 1;i <= n;i++){
        cin>>p[i]>>c[i];
        e[p[i]].push_back(i);
    }
    vector<int>ans = dfs(0);
    cout<<"YES\n";
    for(int i = 0;i < ans.size();i++) a[ans[i]] = i+1;
    for(int i = 1;i <= n;i++)cout<<a[i]<<' ';
    cout<<endl;
    return 0;
}

E1. Madhouse(Easy version)

交互题(QAQ1)

E1

Madhouse (Easy version)

题意:这里有一串长度为n的字符串,每次询问一个区间,会给出这个区间的所有连续子串,问每次询问返回最多(n+1)/2个,请输出该字符串

题解:注意到n(n+1),正好是长度为n的所有连续子串数量,可以第一次询问(1,n),然后询问(2,n),比如abc

1~3     a,b,c,ab,bc,abc

2~2     b,c,bc

然后每一次根据长度减,比如abc减去bc得到第一个元素a,再对第二位进行运算,最后输出答案

#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef pair<int,int> PII;
typedef long long ll;

int n;
map<string,int> mp1[N],mp2[N];


char getChar(string a,string b){
    map<char,int> tmp;
    for(int i = 0;i < a.size();i++) tmp[a[i]]++;
    for(int i = 0;i < b.size();i++) tmp[b[i]]--;
    for(auto it:tmp)if(it.second)return it.first;
}

int main(void){
    cin>>n;
    vector<char> ans;
    if(n == 1){
        cout<<"? 1 1\n";
        string tmp;cin>>tmp;
        cout<<"! "<<tmp<<"\n";
        return 0;
    }
    cout<<"? 1 "<<n<<endl;
    for(int i = 1;i <= n*(n+1)/2;i++){
        string tmp;cin>>tmp;
        sort(tmp.begin(),tmp.end());
        mp1[tmp.size()][tmp]++;
    }
    cout<<"? 2 "<<n<<endl;
    for(int i = 1;i <= n*(n-1)/2;i++){
        string tmp;cin>>tmp;
        sort(tmp.begin(),tmp.end());
        mp2[tmp.size()][tmp]++;
    }
    for(int i = 1;i <= n;i++){///长度为i的string的mp
        for(auto it2: mp2[i]){///将mp1中减去,得到剩余项
            mp1[i][it2.first] -= mp2[i][it2.first];
        }
        for(auto it1: mp1[i]){
            if(it1.second){
                string tmp = "";
                for(int j = 0;j < ans.size();j++) tmp += ans[j];
//                cout<<"%%%% "<<it1.first<<' '<<tmp<<endl;
//                cout<<"###"<<getChar(it1.first,tmp)<<endl;
                ans.push_back(getChar(it1.first,tmp));
            }
        }
    }
    cout<<"! ";
    for(int i = 0;i < ans.size();i++) cout<<ans[i];
    return 0;
}
/*
4
a
a
c
b
aa
ac
cb
aac
acb
aacb
a
c
b
ac
cb
acb
*/

E2. Madhouse(Hard version)

E2

Madhouse (Hard version)

题意:与上一题没有区别,但是询问的次数少了不少,是0.77(n+1)^2,所以直接问1~n不可行了


题解:这题因为返回的字符串数量较少,于是考虑增加询问得到完整字符串,(1,n/2)(1,n/2+1)(1,n)

通过分解因式可以得到这里总共有多少子串,大概是0.75的n多项式,题目要求查询次数小于0.77的n多项式,满足

根据(1,n/2)与(1,n/2+1)之间差值得到前面n/2个数

比如abcde

长度1:a,b,c,d,e,f     出现一次

长度2:ab,bc,cd,de,ef    除了首尾均出现2次,得到首尾元素,然后因为前面已经知道前面n/2个元素,所以得到首尾元素

长度3:abc,bcd,cde 减去长度为2的,这里首尾未知的是ab,de,因为前面已经推出了a,e,且前面n/2个元素已知,所以得到ab然后推出de

长度4,5:同上处理

#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <vector>
using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
typedef pair<int,int> PII;
typedef long long ll;

int nn;
map<string,int> mp1[N],mp2[N];
int mps[30][10010];

char getChar(string a,string b){
    map<char,int> tmp;
    for(int i = 0;i < a.size();i++) tmp[a[i]]++;
    for(int i = 0;i < b.size();i++) tmp[b[i]]--;
    for(auto it:tmp)if(it.second)return it.first;
}

string halfsolve(int n){
    vector<char> ans;
    cout<<"? 1 "<<n<<endl;
    for(int i = 1;i <= n*(n+1)/2;i++){
        string tmp;cin>>tmp;
        sort(tmp.begin(),tmp.end());
        mp1[tmp.size()][tmp]++;
    }
    cout<<"? 2 "<<n<<endl;
    for(int i = 1;i <= n*(n-1)/2;i++){
        string tmp;cin>>tmp;
        sort(tmp.begin(),tmp.end());
        mp2[tmp.size()][tmp]++;
    }
    for(int i = 1;i <= n;i++){///长度为i的string的mp
        for(auto it2: mp2[i]){///将mp1中减去,得到剩余项
            mp1[i][it2.first] -= mp2[i][it2.first];
        }
        for(auto it1: mp1[i]){
            if(it1.second){
                string tmp = "";
                for(int j = 0;j < ans.size();j++) tmp += ans[j];
                ans.push_back(getChar(it1.first,tmp));
            }
        }
    }
    string tt;
    for(int i = 0;i < ans.size();i++) tt += ans[i];
    return tt;
}

int main(void){
    cin>>nn;
    vector<char> ans;
    if(nn == 1){
        cout<<"? 1 1\n";
        string tmp;cin>>tmp;
        cout<<"! "<<tmp<<"\n";
        return 0;
    }
    else if(nn == 2){
        cout<<"? 1 1\n";
        string tmp1,tmp2;
        cin>>tmp1;
        cout<<"? 2 2\n";
        cin>>tmp2;
        cout<<"! "<<tmp1<<tmp2<<endl;
        return 0;
    }
    string halfans = halfsolve(nn+1>>1);///得到前一半的字母序列(消耗n^2/2)
    cout<<"? 1 "<<nn<<endl;
    for(int i = 0;i < nn*(nn+1)/2;i++){///再询问一次全部的得到全部序列
        string tmp;cin>>tmp;
        for(int j = 0;j < tmp.size();j++){
            mps[tmp[j]-'a'][tmp.size()]++;///字母 长度层数
        }
    }
    string anti_halfans = "";
    for(int i = 1;i <= nn/2;i++){///遍历前一半每一位
        for(int j = 0;j < 26;j++){
            int num = mps[j][1]-(mps[j][i+1]-mps[j][i]);
            for(int k = 0;k < i;k++){
                if(halfans[k]-'a' == j)num--;///减去前面出现多少次
            }
            for(int k = 0;k < anti_halfans.size();k++){///减去后面出现次数
                if(anti_halfans[k]-'a' == j)num--;
            }
            if(num > 0){
                anti_halfans += ('a'+j);
                break;
            }
        }
    }
//    cout<<"## "<<halfans<<' '<<anti_halfans<<endl;
    for(int i = 0;i < anti_halfans.size();i++){
        halfans += anti_halfans[anti_halfans.size()-1-i];
    }///正序与逆序合并
    cout<<"! "<<halfans;
    return 0;
}
/*
4
a
b
ab
b
a
b
c
d
ab
bc
cd
abc
bcd
abcd

*/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值