layout: post
title: Codeforces Round 260(Div. 2)
author: "luowentaoaa"
catalog: true
tags:
mathjax: true
- codeforces
- dp
- 分块
- 树直径
- 博弈
A - Laptops (签到)
题意
给你一堆笔记本的价钱和质量问是否有电脑的价格比另一个电脑的价格低的同时质量还更好
价格和质量都小于等于笔记本数量
思路
很棒很有趣的题
一开始我想用树状数组,但是转念一想不对啊这特么才div2A
然后发现价格和质量都小于等于笔记本数量
说明只要有电脑的价格质量不一样那么就肯定会有冲突
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=2e5+50;
const int inf=1e9;
typedef unsigned long long ull;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
cin>>n;
bool flag=false;
for(int i=1;i<=n;i++){
int a,b;
cin>>a>>b;
if(a!=b)flag=1;
}
if(flag)cout<<"Happy Alex"<<endl;
else cout<<"Poor Alex"<<endl;
return 0;
}
B - Fedya and Maths (欧拉函数 or 打表)
题意
计算
n小于等于10的一百万次方
思路
发现直接算很蠢,然后我当时是直接打表计算。因为模数是5所以肯定会有长度最大为5的循环节
or
用欧拉降幂
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=2e5+50;
const int inf=1e9;
typedef unsigned long long ull;
int one[5]={1,1,1,1};
int two[5]={1,2,4,3};
int three[5]={1,3,4,2};
int four[5]={1,4,1,4};
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
string s;
cin>>s;
int len=s.size();
int ans=0;
for(int i=0;i<len;i++){
ans=(ans*10+s[i]-'0')%4;
}
ans=(one[ans]+two[ans]+three[ans]+four[ans])%5;
cout<<ans<<endl;
return 0;
}
C - Boredom (DP)
题意
给出一个堆数 每次取一个a,然后把所有a-1,a+1的数全部移除,之后答案ans+a
求最大的ans
思路
因为顺序没有关系所以我们不考虑顺序
只需要考虑数的数量,
对于一个数他只被他前一个数和他后一个数影响
所以我们设DP[i]为取了前面(1-i)的最大答案值
这个数去不去只被前一个数影响,
所以如果取这个数 那么就不能取前一个数所以
如果不取这个数,那么答案就直接变成前一个的值
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=2e5+50;
const int inf=1e9;
typedef unsigned long long ull;
ll dp[maxn];
ll a[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
cin>>n;
for(int i=1;i<=n;i++){
int aa;
cin>>aa;
a[aa]+=aa;
}
dp[1]=a[1];
ll ans=0;
for(int i=2;i<maxn;i++){
dp[i]=max(dp[i-1],dp[i-2]+a[i]);
ans=max(ans,dp[i]);
}
cout<<ans;
return 0;
}
B - A Lot of Games (字典树+博弈)
题意
给出N个字符串集,
开始字符串为空
两个人游戏,每次的游戏者给字符串加上一个字符 同时保证当前字符串是字符串集合其中一个字符串的前缀
游戏进行K轮,一轮游戏输者变成下一轮游戏的先手
赢了最后一轮游戏的才是最后的赢家
问你是否第一轮的先手有必胜把握
思路
首先把题目化简,变成求一轮游戏的是否能先手胜,
很明显如果一轮游戏先手必赢那么他下一轮比赛就不一定能赢了
如果一轮先手没有必赢的把握那么他比会被对面控制,也就不可能赢
是不是缺少了点啥,对,我们还需要求有没有办法让先手必输的情况;
如果先手可以把握输赢那他就能在他最后一轮先手并且赢 同时可以看出第一轮先手必赢的时候只有奇数轮才可能必赢
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=2e5+50;
const int inf=1e9;
typedef unsigned long long ull;
int tree[maxn][30];
int cnt=0;
bool iswin(int id){
for(int i=0;i<26;i++){
if(tree[id][i]!=-1){
if(!iswin(tree[id][i]))return true;
}
}
return false;
}
bool isfail(int id){
bool fail=true;
for(int i=0;i<26;i++){
if(tree[id][i]!=-1){
fail=false;
if(!isfail(tree[id][i]))return true;
}
}
return fail;
}
char str[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n,k;
cin>>n>>k;
memset(tree,-1,sizeof(tree));
for(int i=1;i<=n;i++){
cin>>str;
int len=strlen(str);
int root=0;
for(int j=0;j<len;j++){
root=tree[root][str[j]-'a']==-1?tree[root][str[j]-'a']=++cnt:tree[root][str[j]-'a'];
}
}
bool win=iswin(0),fail=isfail(0);
if(win){
if(fail)cout<<"First"<<endl;
else{
if(k&1){
cout<<"First"<<endl;
}
else cout<<"Second"<<endl;
}
}
else cout<<"Second"<<endl;
return 0;
}
C - Civilization (树直径+并查集)
题意
给你一个森林
有两种操作
1:求出一个森林中一个树的直径
2:连接两个联通块 使得树的直径最小
思路
求直径已经是模板题了,两次dfs即可
然后保证树的直径最小也是有策略的,直径要么是其中一个树的直径要么是两个树的直径的中心连接
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=1e6+50;
const int inf=1e9;
typedef unsigned long long ull;
int fa[maxn];
vector<int>G[maxn];
ll ans[maxn];
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
void dfs(int now,int pre,int len,int &p,int &dis){
if(len>dis){
dis=len;
p=now;
}
for(int i=0;i<G[now].size();i++){
if(G[now][i]==pre)continue;
dfs(G[now][i],now,len+1,p,dis);
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
G[a].push_back(b);
G[b].push_back(a);
int aa=find(a),bb=find(b);
if(aa!=bb){
fa[bb]=aa;
}
}
for(int i=1;i<=n;i++){
if(fa[i]==i){
int to=-1,dis=-1;
dfs(i,-1,0,to,dis);
dis=0;
dfs(to,-1,0,to,dis);
ans[i]=dis;
}
}
while(q--){
int op,x,y;
cin>>op;
if(op==1){
cin>>x;
x=find(x);
cout<<ans[x]<<endl;
}
else{
cin>>x>>y;
int aa=find(x);
int bb=find(y);
if(aa==bb){
continue;
}
else{
fa[bb]=aa;
ans[aa]=max((ans[aa]+1)/2+(ans[bb]+1)/2+1,max(ans[aa],ans[bb]));
}
}
}
return 0;
}
D - Serega and Fun (分块)
题意
给出一个数组,有两种操作
1:把一个区间循环右移一个数
2:查询一个区间值为K的个数
强制在线
思路
头插尾插第一想到deque
然后分块得出使得两个操作都可以sqrt(n)时间内完成
ps:发现分块新技能,block=pow(n,0.618)黄金分割线比sqrt(n)块了一倍
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=1e5+50;
const int inf=1e9;
typedef unsigned long long ull;
int block;
deque<int>dq[100];
int num[100][maxn];
int n,ans;
void update(){
int l,r;
cin>>l>>r;
l=(ans+l-1)%n;r=(ans+r-1)%n;
if(l>=r)swap(l,r);
int a=l/block,b=r/block,aa=l%block,bb=r%block;
int k=dq[b][bb];
dq[b].erase(dq[b].begin()+bb);
num[b][k]--;
num[a][k]++;
for(int i=a;i<b;i++){
int now=dq[i].back();
dq[i].pop_back();
dq[i+1].push_front(now);
num[i][now]--;
num[i+1][now]++;
}
dq[a].insert(dq[a].begin()+aa,k);
}
void query(){
int l,r,k;
cin>>l>>r>>k;
l=(ans+l-1)%n;r=(ans+r-1)%n;k=(ans+k-1)%n+1;
if(l>=r)swap(l,r);
ans=0;
int a=l/block,b=r/block,aa=l%block,bb=r%block;
if(a==b){
for(int i=aa;i<=bb;i++){
ans+=(dq[a][i]==k);
}
}
else{
for(int i=aa;i<dq[a].size();i++){
ans+=(dq[a][i]==k);
}
for(int i=a+1;i<b;i++){
ans+=num[i][k];
}
for(int i=0;i<=bb;i++){
ans+=(dq[b][i]==k);
}
}
cout<<ans<<endl;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>n;block=pow(n,0.618);
for(int i=0;i<n;i++){
int a;
cin>>a;
dq[i/block].push_back(a);
num[i/block][a]++;
}
int m;
cin>>m;
while(m--){
int op;
cin>>op;
if(op==1)update();
else query();
}
return 0;
}
/*
7
6 6 2 7 4 2 5
7
1 3 6
2 2 4 2
*/