COCI 2016/2017 Round #4题解

COCI 2016/2017 Round #4

这场COCI似乎过于简单啊。。。思维不难,但完全卡爆空间时间。。。

Bridz

题目翻译

分析

水题,按着题意打就是了。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;

int main() {
	freopen("bridz.in","r",stdin);
	freopen("bridz.out","w",stdout);
	int N;char s[20];
	scanf("%d\n",&N);
	int ans=0;
	for(int i=1;i<=N;i++) {
		scanf("%s\n",s);
		for(int j=0;s[j]!='\0';j++) {
			if(s[j]=='A')ans+=4;
			if(s[j]=='K')ans+=3;
			if(s[j]=='Q')ans+=2;
			if(s[j]=='J')ans+=1;
		}
	}
	printf("%d\n",ans);
	return 0;
}

Kartomat

题目翻译

分析

这题只需找出目的地中前缀与之相符的情况,然后输出下一个字母即可。

注意输出的格式。

参考代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int Maxn=50;

int N;
char s[Maxn+5][100];
char t[Maxn+5];
bool ac[Maxn+5];

int main() {
	freopen("kartomat.in","r",stdin);
	freopen("kartomat.out","w",stdout);
	scanf("%d\n",&N);
	for(int i=1;i<=N;i++)
		scanf("%s\n",s[i]);
	scanf("%s",t);
	int len=strlen(t);
	for(int i=1;i<=N;i++) {
		bool flag=false;
		for(int j=0;j<len;j++)
			if(t[j]!=s[i][j]) {
				flag=true;
				break;
			}
		if(flag)continue;
		ac[s[i][len]-'A']=true;
	}
	int cnt=3;
	printf("***");
	for(int i=0;i<26;i++) {
		if(cnt==8) {
			cnt=0;
			puts("");
		}
		if(ac[i])putchar(i+'A');
		else putchar('*');
		cnt++;
	}
	puts("***");
	return 0;
}

Kas

题目翻译

分析

我们记 M = ∑ i = 0 n c i M=\sum_{i=0}^{n}c_i M=i=0nci

我们先得出部分数据的解法:( N ≤ 50 , M ≤ 1000 N\le 50,M\le 1000 N50,M1000

定义 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为当前决定将第 i i i张钱分给某个人,Kile分到 j j j元,Pogi分到 k k k元时剩的最小钱数。

易得状态转移方程: f [ i ] [ j ] [ k ] = min ⁡ { f [ i − 1 ] [ j ] [ k ] + c i , f [ i − 1 ] [ j + c i ] [ k ] , f [ i − 1 ] [ j ] [ k + c i ] } f[i][j][k]=\min\{f[i-1][j][k]+c_i,f[i-1][j+c_i][k],f[i-1][j][k+c_i]\} f[i][j][k]=min{f[i1][j][k]+ci,f[i1][j+ci][k],f[i1][j][k+ci]}

但这个算法的复杂度为 O ( N M 2 ) O(NM^2) O(NM2),必然超时。

注意到我们在分钱时只考虑两人的差值而不是两人具体分到了多少钱,所以我们考虑重新定义状态: f [ i ] [ j ] f[i][j] f[i][j]为当前决定将第 i i i张钱分给某人,两人的差距为 j j j时的第一个人能获得的最大钱数。

得到状态转移方程 f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j + c i ] + c i , f [ i − 1 ] [ ∣ j − c i ∣ ] + max ⁡ ( c i − j , 0 ) } f[i][j]=\max\{f[i-1][j],f[i-1][j+c_i]+c_i,f[i-1][|j-c_i|]+\max(c_i-j,0)\} f[i][j]=max{f[i1][j],f[i1][j+ci]+ci,f[i1][jci]+max(cij,0)}

代码中采用记忆化搜索。

参考代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int Maxn=500;
const int Maxc=1e5;
const int INF=0x3f3f3f3f;

int N,A[Maxn+5];
int f[Maxn+5][Maxc/2+5];
int sum;

int DFS(int i,int j) {
	if(j>sum/2)return -INF;
	int &ret=f[i][j];
	if(ret!=-1)return ret;
	if(i>N)return ret=-INF*(j!=0);
	return ret=max(DFS(i+1,j),max(DFS(i+1,j+A[i])+A[i],DFS(i+1,abs(j-A[i]))+max(0,A[i]-j)));
}

int main() {
	freopen("kas.in","r",stdin);
	freopen("kas.out","w",stdout);
	scanf("%d",&N);
	for(int i=1;i<=N;i++) {
		scanf("%d",&A[i]);
		sum+=A[i];
	}
	memset(f,-1,sizeof f);
	int ans=DFS(1,0);
	ans+=(sum-2*ans);
	printf("%d\n",ans);
	return 0;
}

Rekonstruiraj

题目翻译

分析

这题题解和数据有毒吧。。。

声明:出题人出的数据已经规避了本题解考虑不到的情况。 (其实是这份题解是看了出题人写的题解之后才写的)

首先为了避免浮点误差,我们考虑将每个数乘上 1 0 5 10^5 105(包括左右端点)。

目的就是让我们可以愉快的使用取模和除法了。(但要用long long) ?

不难发现,某些数的因数可能就是答案。

我们穷举每个数的因数,再对这些因数去重后逐个排除。

最后我们剩下一串因数。问题就变成从这些因数中选出最少的个数,使得原数列中每个数都是这些数的倍数。

这是一个经典的NP问题:最小集合覆盖。像题目中这么大的数据是无法在时限内解决的。

所以我们可以考虑贪心。正如开头所说,题目数据已经规避了贪心这一缺陷:-)

所以我们只要找到一个合法的最小的因数,我们就将它在区间里所有的倍数全都删掉。。。

据说使用IDA*能够解决这个问题,但我没写。。。(逃

参考代码

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;
const int Maxn=50;
const ll T=1e5;
const double EPS=0.1;

int N;
ll L,R,A[Maxn+5];
vector<ll> ans;
bool used[Maxn+5];

bool Check(ll x) {
	ll tmp=(L%x==0?L:L-L%x+x);
	while(tmp<=R) {
		ll t=*lower_bound(A+1,A+N+1,tmp);
		if(t!=tmp)
			return false;
		tmp+=x;
	}
	return true;
}

vector<ll> GetNum(ll x) {
	vector<ll> ret;
	for(ll i=1;i*i<=x;i++)
		if(x%i==0)ret.push_back(i),ret.push_back(x/i);
	sort(ret.begin(),ret.end());
	ret.resize(unique(ret.begin(),ret.end())-ret.begin());
	return ret;
}

int main() {
	freopen("rekonstruiraj.in","r",stdin);
	freopen("rekonstruiraj.out","w",stdout);
	scanf("%d %lld %lld",&N,&L,&R);
	L*=T,R*=T;
	for(int i=1;i<=N;i++) {
		double tmp;
		scanf("%lf",&tmp);
		A[i]=tmp*T+EPS;
	}
	for(int i=1;i<=N;i++) {
		if(used[i])continue;
		vector<ll> tmp=GetNum(A[i]);
		for(int j=0;j<(int)tmp.size();j++)
			if(Check(tmp[j])) {
				for(int k=i;k<=N;k++)
					if(A[k]%tmp[j]==0)
						used[k]=true;
				ans.push_back(tmp[j]);
				break;
			}
	}
	for(int i=0;i<(int)ans.size();i++)
		printf("%lf\n",1.0*ans[i]/T);
	return 0;
}

Rima

题目翻译

这里说明一下题意:

押韵的定义是两串的最长公共后缀大于等于较长串的长度减一。

题目中要求的单词序列的长度是指该序列中含有的单词数量。

分析

我们考虑将每个串反转后挂到trie上,那么后缀就变成了前缀。

考虑在trie上做DP。定义 f [ u ] f[u] f[u]为以编号为 u u u的节点结尾的单词后最多能接上的单词数量。

由于trie上每个节点都表示一个单词,我们需要对表示真实存在的单词的节点打上标记,以避免转移时出错。

我们画一下图就可以发现,若该节点对答案有贡献,则该节点对答案的贡献是最多的是两个儿子的 f f f值加上剩余的儿子的数量。

注意当 u u u不是一个真实的节点时,我们应将 f [ u ] f[u] f[u]赋为 0 0 0

注意这道题卡空间,我们不能暴力开长度为 26 26 26数组来存储儿子的信息。

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int Maxn=5e5;
const int Maxlen=3e6;

int N;

struct Trie {
	bool is_real;
	int f;
	vector<pair<char,int> > son;
};
Trie t[Maxlen+5];
int cnt;
int ans;

void addstring(string tmp) {
	int rt=0;
	for(int i=0;i<(int)tmp.length();i++) {
		int nxt=-1;
		for(int j=0;j<(int)t[rt].son.size();j++)
			if(t[rt].son[j].first==tmp[i]) {
				nxt=t[rt].son[j].second;
				break;
			}
		if(nxt==-1) {
			nxt=++cnt;
			t[rt].son.push_back(make_pair(tmp[i],nxt));
		}
		rt=nxt;
	}
	t[rt].is_real=true;
}

void DFS(int rt) {
	int cnt_real=0;
	pair<int,int> maxx;
	maxx=make_pair(0,0);
	for(int i=0;i<(int)t[rt].son.size();i++) {
		int v=t[rt].son[i].second;
		DFS(v);
		cnt_real+=t[v].is_real;
		maxx=max(maxx,make_pair(maxx.first,t[v].f));
		maxx=max(maxx,make_pair(t[v].f,maxx.first));
		t[rt].f=max(t[rt].f,t[v].f);
	}
	if(t[rt].is_real)
		t[rt].f+=max(1,cnt_real);
	else t[rt].f=0;
	ans=max(ans,maxx.first+maxx.second+t[rt].is_real+max(0,cnt_real-2));
}

int main() {
	freopen("rima.in","r",stdin);
	freopen("rima.out","w",stdout);
	cin>>N;
	for(int i=1;i<=N;i++) {
		string s;
		cin>>s;
		reverse(s.begin(),s.end());
		addstring(s);
	}
	DFS(0);
	printf("%d\n",ans);
	return 0;
}

Osmosmjerka

题目翻译

分析

这题完全是卡哈希的

考虑到 K K K过大,我们必须使用哈希来表示一个字符串,而且还要带倍增的。

由于这道题我们可以发现,其实中间的字符串很多是重复的,我们只需要判断其中一小部分就可以了。

我们可以发现,最多判 N × M N\times M N×M就可以判断一个串是否相等。

所以我们就可以愉快地使用倍增哈希了。?

然而,这题卡空间。。。卡时间。。。?(妙啊

我们只能让哈希自然溢出而且还不能开long long于是双哈希闪亮登场

然后除以总方案数 ( 8 N M ) 2 (8NM)^2 (8NM)2。。。

哈希模数有毒要多选几下

参考代码

#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;

typedef unsigned int ui;
typedef long long ll;
const int Maxn=500;
const int Maxlogk=21;
const int dir[][2]={{1,0},{0,1},{0,-1},{-1,0},{1,-1},{-1,1},{1,1},{-1,-1}};
const ui Hash[2]={1183531,1041221};
//const ll Mod[2]={(ll)1e9+7,999999937};

inline ll GCD(ll x,ll y) {
	if(y==0)return x;
	return GCD(y,x%y);
}

int N,M;
ll K;
char s[Maxn+5][Maxn+5];
ui pow_2[2][Maxlogk+5];
struct HashNode {
	ui h[2];
	void add(const HashNode &rhs,int len) {
		for(int i=0;i<2;i++) {
			h[i]=(h[i]*pow_2[i][len])/*%Mod[i]*/+rhs.h[i];
		//	h[i]%=Mod[i];
		}
	}
};
bool operator < (const HashNode &lhs,const HashNode &rhs) {
	for(int i=0;i<2;i++)
		if(lhs.h[i]!=rhs.h[i])
			return lhs.h[i]<rhs.h[i];
	return false;
}
HashNode f[Maxn+5][Maxn+5][Maxlogk+5];

map<HashNode,int> cnt;

int main() {
	freopen("osmosmjerka.in","r",stdin);
	freopen("osmosmjerka.out","w",stdout);
	scanf("%d %d %lld\n",&N,&M,&K);
	for(int i=0;i<N;i++)
		scanf("%s",s[i]);
	for(int k=0;k<8;k++) {
		pow_2[0][0]=Hash[0],pow_2[1][0]=Hash[1];
		for(int i=1;i<=Maxlogk;i++) {
			pow_2[0][i]=(pow_2[0][i-1]*pow_2[0][i-1])/*%Mod[0]*/;
			pow_2[1][i]=(pow_2[1][i-1]*pow_2[1][i-1])/*%Mod[1]*/;
		}
		for(int i=0;i<N;i++)
			for(int j=0;j<M;j++) {
				f[i][j][0].h[0]=s[i][j]-'a'/*%Mod[0]*/;
				f[i][j][0].h[1]=s[i][j]-'a'/*%Mod[1]*/;
			}
		int dx=dir[k][0],dy=dir[k][1];
		ll len=1;
		for(int log=1;log<=Maxlogk;log++) {
			for(int i=0;i<N;i++)
				for(int j=0;j<M;j++) {
					f[i][j][log]=f[i][j][log-1];
					f[i][j][log].add(f[((i+dx*len)%N+N)%N][((j+dy*len)%M+M)%M][log-1],log-1);
				}
			len*=2;
		}
		for(int i=0;i<N;i++)
			for(int j=0;j<M;j++) {
				len=K;
				HashNode t;
				int x=i,y=j;
				t.h[0]=t.h[1]=0;
				for(int log=Maxlogk;log>=0;log--)
					if((1<<log)&len) {
						len-=(1<<log);
						t.add(f[x][y][log],log);
						x=((x+dx*(1<<log))%N+N)%N;
						y=((y+dy*(1<<log))%M+M)%M;
					}
				cnt[t]++;
			}
	}
	ll x=0,y=1LL*(N*M*8)*(N*M*8);
	for(map<HashNode,int>::iterator it=cnt.begin();it!=cnt.end();it++)
		x+=(1LL*(it->second)*(it->second));
	ll tmp=GCD(x,y);
	x/=tmp,y/=tmp;
	printf("%lld/%lld\n",x,y);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值