[Codeforces #190] Tutorial

Link:

Codeforces #190 传送门

A:

明显答案为$n+m-1$且能构造出来

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
int n,m;

int main()
{
    scanf("%d%d",&n,&m);
    printf("%d
",n+m-1);
    for(int i=1;i<=m;i++)
        printf("1 %d
",i);
    for(int i=2;i<=n;i++)
        printf("%d 1
",i);
    return 0;
}
Problem A

B:

我是采取对余数分类讨论的方式

但如果余数为0,2,2且原数中恰好有一个0时要特判!因此WA了一次……

不过完全可以通过采取其他方法来避免这个问题:

暴力枚举散装分别为0,1,2时的总个数即可

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
int r,g,b,tr,tg,tb,res;

int main()
{
    scanf("%d%d%d",&r,&g,&b);
    res+=r/3;res+=g/3;res+=b/3;
    tr=r;tg=g;tb=b;
    r%=3;g%=3;b%=3;
    if(r>g) swap(r,g);
    if(r>b) swap(r,b);
    if(g>b) swap(g,b);
    
    if(r==1) res++;
    else if(r==2) res+=2;//如果%3=0的值原来就是0不能增加! 
    else if(r==0&&g==2&&b==2&&tr&&tg&&tb) res++;
    printf("%d",res);
    return 0;
}
Problem B

C:

此题只要对第一次循环中的所有位置判断能否通过整的循环到达终点即可

不过有2点要注意:1、特判整循环最终$X,Y$为0的情况 2、循环数非负

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
const int INF=1<<30;
P cur,sum,T;char s[105];

void move(P &v,int pos)
{
    if(s[pos]=='U') v.Y++;
    if(s[pos]=='D') v.Y--;
    if(s[pos]=='L') v.X--;
    if(s[pos]=='R') v.X++;
}
int main()
{
    scanf("%d%d%s",&T.X,&T.Y,s);
    if(T==P(0,0)) return puts("Yes"),0;
    
    int len=strlen(s);bool f=0;
    for(int i=0;i<len;i++) move(sum,i);
    for(int i=0;i<len;i++)
    {
        move(cur,i);//可能mod出-1,因此初始值设为INF 
        int mx=INF,my=INF,cx=INF,cy=INF;
        if(sum.X) mx=(T.X-cur.X)%sum.X,cx=(T.X-cur.X)/sum.X;
        if(sum.Y) my=(T.Y-cur.Y)%sum.Y,cy=(T.Y-cur.Y)/sum.Y;
        if(mx==INF&&my==INF&&cur==T){f=1;break;}
        if(mx==INF&&cur.X==T.X&&!my&&cy>=0){f=1;break;}
        if(my==INF&&cur.Y==T.Y&&!mx&&cx>=0){f=1;break;}
        if(!mx&&!my&&cx==cy&&cx>=0){f=1;break;}
    }
    puts(f?"Yes":"No");
    return 0;
}
Problem C

这题一开始都WA了一次……

原因是判断整循环的$X,Y$为0时是用余数的初始值是否为-1来判断的

但实际上余数结果本来就可能是-1从而和初始值冲突

以后对余数设初始值时要注意设为$INF$这样不可能达到的值……

D:

很容易的贪心,发现只要考虑两种情况:

1、只攻击$ATK$

2、将$DEF$和$ATK$都攻击完

将3个序列都排序后

对于情况1,每次用最大的攻击$ATK$最小的,直到不能攻击为止必是最优解

对于情况2,先用尽可能接近的攻击完$DEF$,接下来只要能将$ATK$都打完结果就是两个序列和的差

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
#define pb push_back
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
const int MAXN=105;
char s[MAXN];
int n,m,x;
vector<int> atk,def,dat;

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s%d",s,&x);
        if(s[0]=='A') atk.pb(x);
        if(s[0]=='D') def.pb(x);
    }
    for(int i=1;i<=m;i++) scanf("%d",&x),dat.pb(x);
    sort(dat.begin(),dat.end());
    sort(atk.begin(),atk.end());
    sort(def.begin(),def.end());
    
    int res1=0,res2=0,sum1=0,sum2=0;bool f=1;
    for(int i=0;i<atk.size()&&i<dat.size();i++)
        if(dat[m-i-1]>=atk[i]) res1+=dat[m-i-1]-atk[i];
        else break;
    for(int i=(int)def.size()-1;i>=0;i--)
    {
        vector<int>::iterator it=upper_bound(dat.begin(),dat.end(),def[i]);
        if(it==dat.end()){f=0;break;}
        dat.erase(it);
    }
    int sz=dat.size();
    if(sz<atk.size()) f=0;
    for(int i=0;i<sz;i++) sum1+=dat[i];
    for(int i=0;i<atk.size();i++) sum2+=atk[i];
    for(int i=(int)atk.size()-1;i>=0;i--)
    {
        vector<int>::iterator it=lower_bound(dat.begin(),dat.end(),atk[i]);
        if(it==dat.end()){f=0;break;}
        dat.erase(it);
    }    
    
    res2=f?sum1-sum2:0;
    printf("%d",max(res1,res2));
    return 0;
}
Problem D

注意在情况2的第二个阶段不能采取和情况1相同的方法

因为此时的目标是将$ATK$都打完,因此应和攻击$DEF$时采取的方法一样:贪心选取第一个更大的数

E:

构造题

一般在树上构造考虑两种方式:

1、从底到上(对应此题为点分治的方法)

2、从顶至下(从叶子节点向上递归)

方法1:

发现如果将一个节点设为$A$,那么剩下的子树都变成了独立的问题,只不过不能再出现$A$

因此考虑将第$i$层点分治的树根设为$A+i$,这样保证有解因为$log(1e5)<26$

方法2:

考虑从叶子节点向上递归构造

每次向上传递该点的值和该子树中比根大的值,因为比根小的明显不用考虑了

选择时将儿子上传的值合并,选取比出现2次大的第一个没有出现的值

其实就是不合法的值分类后去除:

1、点对在两个不同儿子且值更大(因此选取比出现2次大的)

2、该点本身和子树中一个较大的值组成点对(因此选取为出现过的)

注意在返回时要将比选取值小的个数清零,因为后面不用考虑了

这样保证使用个数不超过26的正确性可以用势能法证明,然而我并不会

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=1e5+10;
struct edge{int nxt,to;}e[MAXN<<2];
int n,x,y,head[MAXN],cnt[MAXN][30],res[MAXN],tot;

void add(int from,int to)
{e[++tot].nxt=head[from];e[tot].to=to;head[from]=tot;}
void dfs(int x,int anc)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        if(e[i].to==anc) continue;
        dfs(e[i].to,x);
        for(int j=0;j<26;j++)
            cnt[x][j]+=cnt[e[i].to][j];
    }
    int pos1=26,pos2=0;
    for(int i=0;i<26;i++)
        if(cnt[x][i]>=2){pos1=i;break;}
    for(int i=pos1-1;i>=0;i--)
        if(!cnt[x][i]){pos2=i;break;}
    cnt[x][pos2]=1;res[x]=pos2;
    for(int i=pos2+1;i<26;i++) cnt[x][i]=0;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
        scanf("%d%d",&x,&y),add(x,y),add(y,x);
    dfs(1,0);
    for(int i=1;i<=n;i++)
        printf("%c ",'A'+res[i]);
    return 0;
}
Problem E
原文地址:https://www.cnblogs.com/newera/p/9507271.html