首先说说做CF的感受把:大多数是智力题,而且偏向某个方向很严重,而且答案具有开放性,可以有多种解。所以这轮的主题也就是“贪心”。
A. Ciel and Dancing
这题题目建模,给定一张二部图,左边N各节点,右边M个节点,要求一种连接法连接左右两部,使的所有边的点都有一个是只有一条边的。
那么贪心思想,首先让n1与所有m结合,然后在用m2与所有n(除了n1之外)相连就行了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m)){
if(n <= m){
printf("%d\n",n+m-1);
for(int i=1;i<=m;i++)
printf("1 %d\n",i);
for(int i=2;i<=n;i++)
printf("%d 1\n",i);
}else{
printf("%d\n",n+m-1);
for(int i=1;i<=n;i++)
printf("%d 1\n",i);
for(int i=2;i<=m;i++)
printf("1 %d\n",i);
}
}
return 0;
}
题意:有三种方法构成花束,三朵红或者绿或者蓝,还有各种颜色各一朵,先给出三种花朵的数目,求能组成的最多的花束。
贪心:比较几种构成法,求其中那个最多的那种。(想不到我也有今天,对不起勒,题解太简略了,赶时间啊)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
int r,g,b;
while(~scanf("%d%d%d",&r,&g,&b)){
int sum123 = r/3 + g/3 + b/3 + min(r%3,min(g%3,b%3));
int sum12 = r/3 + g/3 + min(r%3,min(g%3,b)) + (b-min(r%3,min(g%3,b)))/3;
int sum23 = g/3 + b/3 + min(g%3,min(b%3,r)) + (r-min(g%3,min(b%3,r)))/3;
int sum13 = r/3 + b/3 + min(r%3,min(b%3,g)) + (g-min(r%3,min(b%3,g)))/3;
int sum1 = r/3 + min(r%3,min(b,g))+(b-min(r%3,min(b,g)))/3+(g-min(r%3,min(b,g)))/3;
int sum2 = g/3 + min(g%3,min(r,b))+(r-min(g%3,min(r,b)))/3+(b-min(g%3,min(r,b)))/3;
int sum3 = b/3 + min(b%3,min(r,g))+(r-min(b%3,min(r,g)))/3+(g-min(b%3,min(r,g)))/3;
int sum = min(r,min(g,b))+(r-min(r,min(g,b)))/3+(g-min(r,min(g,b)))/3+(b-min(r,min(g,b)))/3;
int ans;
ans = max(sum1,sum2);
ans = max(ans,sum3);
ans = max(ans,sum12);
ans = max(ans,sum13);
ans = max(ans,sum23);
ans = max(ans,sum123);
ans = max(ans,sum);
cout<<ans<<endl;
}
}
C. Ciel and Robot
题意:给定一个走法序列,只能重复该序列,不能改变顺序,问该走法序列能否帮助机器人走到目标点(a,b )。
首先求出一次走法序列能向右走多少,向上走多少,判断该位移能否满足目标点,如不行,然后枚举走法序列每一步的位移,看看该位移是否满足被目标点整除的情况(大概是这个意思,赶时间啊)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 106
char str[maxn];
int main()
{
int a,b;
while(~scanf("%d%d",&a,&b)){
getchar();
gets(str);
int len = strlen(str);
int flag = 0;
int x = 0,y = 0;
for(int i=0;i<len&&flag==0;i++){
if(x==a && y==b) flag = 1;
if(str[i]=='U') y++;
if(str[i]=='D') y--;
if(str[i]=='L') x--;
if(str[i]=='R') x++;
if(x==a && y==b) flag = 1;
}
if(flag == 0){
int q=0,p=0;
for(int i=0;i<len&&flag==0;i++){
if(str[i]=='U') q++;
if(str[i]=='D') q--;
if(str[i]=='L') p--;
if(str[i]=='R') p++;
if(x==0 && y==0){
if(p==a && q==b) flag = 1;
}else if(x==0 && y!=0){
if(p==a && (b-q)%y==0 && (b-q)/y>=0)
flag = 1;
}else if(y==0 && x!=0){
if(q==b && (a-p)%x==0 && (a-p)/x>=0)
flag = 1;
}else{
if((a-p)%x==0&&(b-q)%y==0&&(b-q)/y==(a-p)/x&&(a-p)/x>=0)
flag = 1;
}
}
}
if(flag) puts("Yes");
else puts("No");
}
return 0;
}
D. Ciel and Duel
题意:游戏王玩过吧,给定你的攻击怪兽的攻击力,给出对手的攻击性怪兽的攻击力、防守型怪兽的防守力,还可以直接对玩家进行伤害,计算能造成的最多伤害。
首先,将我方怪兽攻击力高低排序,对方怪兽防守力、攻击力分别排序
贪心策略:(1)考虑对玩家进行直接伤害,即用我方怪兽消灭对方所有怪兽,剩余怪兽直接伤害,按照这种理论,我们就需要首先把所有的防守型怪兽消灭,而且是利用最小大过其防守力的怪兽去ATK,然后,如果还有怪兽,就用大过对方攻击型怪兽的最小的攻击力的怪兽消灭对方攻击性怪,最后剩下的怪兽直接攻击。
(2)不考虑直接伤害,那么用最大攻击力的怪去打对方攻击性攻击力最小的怪,如此做,即可。
最后比较两种方法的好坏即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 106
int n,m;
int top1,top2;
int a[maxn];
bool hash[maxn];
int b1[maxn]; //进攻
int b2[maxn]; //防守
int f1(){
int x1=0,x2=0;
int ret = 0;
memset(hash,false,sizeof(hash));
for(int i=0;i<m&&x2<top2;i++) {
if(hash[i]==false && a[i]>b2[x2]){
hash[i] = true;
x2++;
}
}
for(int i=0;i<m&&x1<top1;i++){ //这里一定是用攻击力小的击败对手atk型怪兽,然后用高攻击的怪直接攻击
if(hash[i]==false && a[i]>=b1[x1]){
ret += a[i] - b1[x1];
x1++;
hash[i] = true;
}
}
if(x2==top2 && x1==top1){
for(int i=0;i<m;i++){
if(hash[i]==false) ret += a[i];
}
}
return ret;
}
int f2(){
int x1 = 0,x2=0;
int ret = 0;
memset(hash,false,sizeof(hash));
for(int i=m-1;i>=0 && x1<top1;i--){
if(hash[i]==false&&a[i]>=b1[x1]){
ret += a[i] - b1[x1];
x1++;
hash[i]=true;
}
}
for(int i=0;i<m&&x2<top2;i++){
if(hash[i]==false&&a[i]>b2[x2]){
hash[i] = true;
x2++;
}
}
if(x1==top1 && x2==top2){
for(int i=0;i<m;i++){
if(hash[i]==false) ret += a[i];
}
}
return ret;
}
int main()
{
while(~scanf("%d%d",&n,&m)){
getchar();
char cmd[5];
int ss;
top1 = 0,top2 = 0;
for(int i=0;i<n;i++){
scanf("%s%d",cmd,&ss);
if(cmd[0]=='A') b1[top1++] = ss; //进攻
else b2[top2++] = ss; //防守
}
for(int i=0;i<m;i++){
scanf("%d",&a[i]);
}
sort(a,a+m);
sort(b1,b1+top1);
sort(b2,b2+top2);
int sum1 = f1();
int sum2 = f2();
int ans = max(sum1,sum2);
cout<<ans<<endl;
}
return 0;
}
E. Ciel the Commander
题意:给定一棵树,要求标级别(A~Z,A最小),相邻两个点级别不能相同,相同级别的两个点之间必有比他们级别大的点,求一种标级方案。
这是最有价值的一道题目,利用自底向上的算法,假设我们已经表记好了子树根节点T1,T2,T3.....TK,如何标记他们的根节点,首先只要有两个子根节点级别相同,则根节点必须大于之,不能出现与子根节点的级别相同的级别。
这里关键技术又变为如何dfs这个树并标记了,有用到了强大的二进制了,只有26个字母么,11111代表用了A~E……,懂了吧。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxm 300006
#define maxn 100005
typedef struct {
int to,next;
}Edge;
Edge e[maxm];
int head[maxn];
int n;
int e_cnt;
struct Tree{
int stu; //二进制表示使用了哪一种等级,如10代表使用了B等级
int rank; //等级
}tree[maxm];
bool flag;
void init(){
memset(head,-1,sizeof(head));
e_cnt = 0;
}
void add(int u,int v){
e[e_cnt].to = v;
e[e_cnt].next = head[u];
head[u] = e_cnt;
e_cnt++;
}
void dfs(int x,int fa){
int stu = 0;
int dub = 0; //度数
for(int i=head[x];i!=-1;i=e[i].next){
int v = e[i].to;
if(v == fa) continue;
dfs(v,x);
dub = max(dub,stu&tree[v].stu);
stu |= tree[v].stu;
}
int &p = tree[x].stu;
p = 1;
while(p<=dub || (p&stu)) p <<= 1;
for(int i=(p<<1);i<(1<<26);i<<=1) if(i&stu) p |= i;
for(int i=0;i<26;i++){
if((1<<i)&tree[x].stu){
tree[x].rank = 26 - i - 1;
if(tree[x].rank<0 || tree[x].rank>26) flag = true;
return ;
}
}
}
int main()
{
while(~scanf("%d",&n)){
memset(tree,0,sizeof(tree));
init();
flag = false;
int u,v;
for(int i=0;i<n-1;i++){
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
if(flag ) cout<<"Impossible!";
else{
for(int i=1;i<=n;i++) cout<<char(tree[i].rank+'A')<<" ";
}
cout<<endl;
}
return 0;
}