Codeforces Round #300 解题报告(ABCDEF)

本文解析了CodeForces平台上的六道算法题,包括字符串处理、贪心算法、图论等,提供了详细的解题思路及代码实现。

Cutting Banner

        给一个串,从头尾各切一段下来(也可以不切),问头+尾能否组成"CODEFORCES"。对头尾进行匹配分别得到长度,然后长度相加看是否>=10。


Quasi Binary

        给一个数,问这个数最少能用多少个只包含0和1的数相加得到,输出任意解(数都是十进制)。贪心,但是要注意,设当前还需要凑cur,不能去找最大的不超过cur的只含01的数。比如20,如果减去11,剩下部分还要9个1。。远不如10+10。正确做法是按位来看,如果某一位大于0,那么该位填1,否则填0。


Tourist's Notes

        爬山,每天海拔只能和前一天一样或增减1。给出总共爬的天数,记录下了某些天的海拔,问最高可能到达海拔。和之前的一场CF的某题几乎一样。。设相邻两个记录相距d(i+1)-d(i)天,高度为h(i)和h(i+1),中间可到达最大高度就是(d(i+1)-d(i)+h(i)+h(i+1))/2。第一个记录前和最后一个记录后让它逐天递增1就好,找出最大。


Weird Chess

        在n*n的棋盘上,有个(dx,dy)的集合,定义了当棋子在(x,y)可以跳到(x+dx,y+dy)。给出一个棋盘,上面放了若干棋子,标出了这些棋子能跳到的所有位置。现在(2*n-1)*(2*n-1)的棋盘中心有一枚棋子,要求在上面画出(dx,dy)集合(任意解)。

        我的做法是暴力。对每一个(dx,dy),用棋盘上所有棋子去验证这个(dx,dy)是否可行。


Demiurges Play Again

        一棵树,有m个叶子,所有叶子标有号1~m。两个人轮流从根开始往下走,走到叶子结束。先手希望走到的叶子号尽可能大,后手希望尽可能小,两个人都是最优决策。问先手安排叶子号和后手安排叶子号时,走到的结果分别是什么。

        这题目出得好,我弄了很久才理解。首先假设树的所有叶子号我们都已经知道。定义MAX(u)是希望最大的人先手u为根的子树会走到的结果,MIN(u)是希望最小的人先手u为根的子树会走到的结果。vi是u的孩子,那么,MAX(u)就等于MIN(vi)中的最大值,MIN(u)就等于MAX(vi)中的最小值。

        但是,树的叶子号我们不知道(可以安排)。只考虑子树中每个叶子的相对大小,就是在子树中所有叶子的rank(从大到小依次1,2,3...)。记f(u)为希望最大的人先行动u子树得到的rank,g(u)为希望最小的人先行动u子树得到的rank。那么对于叶子有f(u)=g(u)=1,非叶子有f(u)=min(g(vi)),g(u)=sum(f(vi))。最后结果就是m-f(1)+1,g(1)(因为对称性)。

#include <bits/stdc++.h>
using namespace std;

const int maxn=2e5+10;
const int INF=1e9;

int n;
int head[maxn];
int pre[maxn];
int to[maxn];
int f[maxn];
int g[maxn];
int tote;

void addedge(int u,int v){
	to[tote]=v;
	pre[tote]=head[u];
	head[u]=tote++;
}


static int leafcnt=0;
void dfs(int u){
	f[u]=INF;
	for(int i=head[u];~i;i=pre[i]){
		int v=to[i];
		dfs(v);
		if(g[v]<f[u])f[u]=g[v];
		g[u]+=f[v];
	}
	if(!g[u]){
		leafcnt++;
		g[u]=f[u]=1;
	}
}


int main(){
	memset(head,-1,sizeof(head));
	tote=0;
	
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		addedge(u,v);
	}
	dfs(1);

	cout<<leafcnt-f[1]+1<<" "<<g[1]<<endl;
	return 0;
}



A Heap of Heaps

        给一个n个元素的数组。问如果把这个数组当成k(k=1,2,3...n-1)叉堆(小顶堆),分别有多少个元素不合法。不合法是指节点小于自己的父节点。

        首先对于k叉堆,每个元素孩子在数组中的下标范围是可以用O(1)计算出来的。我们可以将这n个数从小到大排序,记录下每个元素原来的位置。从小到大将元素的位置加入到树状数组里(其实是先查询),并查询树状数组,自己孩子的那个区间内有多少比自己小的。注意一个细节,先查询完同样大小的所有元素,再更新树状数组。详见代码。

        值得一提的是复杂度。因为2叉树大致有n/2个节点有孩子,3叉树大致有n/3,4叉n/4...调和级数的和趋近于ln(n),而树状数组的操作复杂度是log(n)。因此,总复杂度是nlognlogn。。。

#include <bits/stdc++.h>

using namespace std;

const int maxn=2e5+10;
int n;
int a[maxn];
int rank[maxn];

bool cmp(int x,int y){
	return a[x]<a[y];
}

inline int Lmostson(int id,int k){
	return id*k-k+2;
}

inline int Rmostson(int id,int k){
	return id*k+1;
}

inline int lowbit(int x){
	return x&(-x);
}

int c[maxn]; 
void update(int pos){
	while(pos<=n){
		c[pos]++;
		pos+=lowbit(pos);
	}
}

int query(int pos){
	int re=0;
	while(pos){
		re+=c[pos];
		pos-=lowbit(pos);
	}
	return re;
}


int ans[maxn];

int main(){
	cin>>n;
	a[0]=-1e9-1;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	
	for(int i=0;i<=n;i++)rank[i]=i;
	
	sort(rank,rank+n+1,cmp);
	
	for(int rnk=1;rnk<=n;rnk++){	//枚举排第rnk位的数 
		int pos=rank[rnk];
		for(int k=1;k<n;k++){		//k叉树 
			int lson=Lmostson(pos,k);
			if(lson>n)break;		//没有孩子了,跳出 
			int rson=Rmostson(pos,k);
			if(rson>n)rson=n;
			//查询孩子中有多少个小于自己的
			int tmp=query(rson)-query(lson-1);
			ans[k]+=tmp;
		}
		//只有当下一个数不同才去更新 
		if(a[rank[rnk]]!=a[rank[rnk+1]]){
			int cur=rnk;
			while(1){
				update(rank[cur]);
				cur--;
				if(a[rank[cur]]!=a[rank[cur+1]])break;
			}
		}
	}
	for(int i=1;i<n;i++){
		printf("%d ",ans[i]);
	}
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值