COCI 2016/2017 Round #3 题解

COCI 2016/2017 Round #3

这套题代码量似乎有些大呀。。。前五题代码都已经破4KB了。。。

而且还要卡空间、卡常数。。。

Imena

题目翻译

COCI 2016/2017 Round #3 题解_第1张图片

分析

细节模拟题,注意可能会出现名字只有一个字的情况。

参考代码

#include
#include
#include
using namespace std;

int main() {
	freopen("imena.in","r",stdin);
	freopen("imena.out","w",stdout);
	int N;
	scanf("%d\n",&N);
	char s[1000];
	int num=0;
	bool output=false;
	while(scanf("%s ",s)!=EOF) {
		int len=strlen(s);
		if(s[len-1]=='.'||s[len-1]=='?'||s[len-1]=='!')
			len--,output=true;
		bool flag=('A'<=s[0]&&s[0]<='Z');
		if(flag==true) {
			for(int i=1;i<len;i++)
				if(!('a'<=s[i]&&s[i]<='z')) {
					flag=false;
					break;
				}
		}
		if(flag==true)num++;
		if(output==true) {
			printf("%d\n",num);
			num=0,output=false;
		}
	}
	return 0;
}

Pohlepko

考试的时候就不该写广搜的。。。空间爆了。。。100分只剩了10几分。。。

题目翻译

COCI 2016/2017 Round #3 题解_第2张图片

分析

我们很容易得出部分分的做法:比较两个字母的大小,将字母小的留下,字母大的舍弃即可。

所以我们考虑正解:对于有相同字母的两个位置,我们都将它们保存下来,直到它们分出字典序大小即可。

参考代码

#include
#include
#include
#include
#include
using namespace std;

const int Maxn=2000;

int N,M;
char s[Maxn+5][Maxn+5];

bool vis[Maxn+5][Maxn+5];
void Solve(int sx,int sy) {
	vector<pair<int,int> > now,nxt;
	now.push_back(make_pair(sx,sy));
	while(!now.empty()) {
		putchar(s[now.back().first][now.back().second]);
		char ch='z'+1;
		for(int i=0;i<now.size();i++) {
			int x=now[i].first,y=now[i].second;
			if(x!=N)ch=min(ch,s[x+1][y]);
			if(y!=M)ch=min(ch,s[x][y+1]);
		}
		nxt.clear();
		for(int i=0;i<now.size();i++) {
			int x=now[i].first,y=now[i].second;
			if(x!=N&&ch==s[x+1][y]&&!vis[x+1][y]) {
				vis[x+1][y]=true;
				nxt.push_back(make_pair(x+1,y));
			}
			if(y!=M&&ch==s[x][y+1]&&!vis[x][y+1]) {
				vis[x][y+1]=true;
				nxt.push_back(make_pair(x,y+1));
			}
		}
		now=nxt;
	}
	puts("");
}

int main() {
	freopen("pohlepko.in","r",stdin);
	freopen("pohlepko.out","w",stdout);
	scanf("%d %d",&N,&M);
	for(int i=1;i<=N;i++)
		scanf("%s",s[i]+1);
	Solve(1,1);
	return 0;
}

Kronican

我最后一个点被卡常数了。。。

题目翻译

COCI 2016/2017 Round #3 题解_第3张图片

分析

看一下数据范围 1 ≤ N ≤ 20 1\le N\le20 1N20,不用说,直接高耗了。。。

定义状态 f [ s ] f[s] f[s]为将集合 s s s中的杯子全部倒进其他杯子所需的代价最小值。

则易得状态转移方程 f [ s ∪ i ] = f [ s ] + c ( i , s ) ( i ∉ s ) f[s\cup i]=f[s]+c(i,s)(i\notin s) f[si]=f[s]+c(i,s)(i/s)

其中 c ( i , s ) c(i,s) c(i,s)表示将第 i i i个杯子中的水倒入未被倒入其他杯子中的水的最小代价。

最终答案为 min ⁡ { f [ s ] ∣ ∣ s ∣ ≥ N − K } \min\{f[s]||s|\ge N-K\} min{f[s]sNK}

这个枚举各个状态所需时间复杂度为 O ( 2 N ) O(2^N) O(2N),转移为 O ( N ) O(N) O(N),计算 c ( i , s ) c(i,s) c(i,s) O ( N ) O(N) O(N),所以总时间复杂度为 O ( 2 N × N 2 ) O(2^N\times N^2) O(2N×N2),再加上一些卡常数的技巧,刚好卡过。

注意这道题中,将第 i i i个杯子倒入第 j j j个杯子的代价可能不等于将第 j j j个杯子倒入第 i i i个杯子的代价。

据说这题暴搜剪枝也能过???

参考代码

#include
#include
#include
using namespace std;

const int Maxn=20;
const int INF=0x3f3f3f3f;

int N,K;
int G[Maxn+5][Maxn+5];

int BitCnt(int x) {
	int ret=0;
	while(x) {
		ret++;
		x-=(x&(-x));
	}
	return ret;
}

int dp[(1<<Maxn)+5];
bool used[Maxn+5];
int cost(int num,int t) {
	int ret=INF;
	for(int i=1;i<=N;i++)
		if(i!=num&&!used[i])
			ret=min(ret,G[num][i]);
	return ret;
}

int main() {
	freopen("kronican.in","r",stdin);
	freopen("kronican.out","w",stdout);
	scanf("%d %d",&N,&K);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=N;j++)
			scanf("%d",&G[i][j]);
	if(N==K) {
		puts("0");
		return 0;
	}
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for(int s=0;s<=(1<<N)-1;s++) {
		for(int i=0;i<=N;i++)
			used[i]=false;
		for(int i=1;i<=N;i++)
			if(s&(1<<(i-1)))
				used[i]=true;
		for(int i=1;i<=N;i++)
			if(!(s&(1<<(i-1)))) {
				int t=s|(1<<(i-1));
				dp[t]=min(dp[t],dp[s]+cost(i,s));
			}
	}
	int ans=INF;
	for(int s=1;s<(1<<N);s++)
		if(BitCnt(s)>=N-K)
			ans=min(ans,dp[s]);
	printf("%d\n",ans); 
	return 0;
}

Kvalitetni

题目翻译

COCI 2016/2017 Round #3 题解_第4张图片
题目描述有些不清,我补充一下:

× , + \times,+ ×,+按照正常的算法算,且一个括号内,只有一种符号。

分析

为了处理出计算顺序,我们先利用DFS构建出表达式树。

对于每层数字,我们建立一个数组来记录它们的最大值。

若我们找到一个单独的?,我们就直接将 Z 1 Z_1 Z1返回。

当我们找到一个(,我们应递归下去,并把返回值加进数组中。

当我们找到符号时,要记录下来。

当我们找到一个)时,这时表明我们已经将这层的质量表达式的每个元素的最大值找完了,接下来需要算结果了。

对于加法,我们直接将每个元素的最大值加起来,与 Z k Z_k Zk进行比较后返回即可。

对于乘法,我们需要利用均值不等式: ∏ i = 1 k a i ≤ 1 k ∑ i = 1 k a i \prod_{i=1}^{k}a_i\le\frac{1}{k}\sum_{i=1}^{k}a_i i=1kaik1i=1kai

又因为 ∑ i = 1 k a i ≤ Z k \sum_{i=1}^{k}a_i\le Z_k i=1kaiZk

所以平均每个值为 Z k k \frac{Z_k}{k} kZk

但如何处理当一个数的最大值已经小于这个平均值?

很简单,将这个少掉的部分平均一下,补充在其它位置上的数上。

最后将所有答案乘起来返回即可。

参考代码

#include
#include
#include
using namespace std;

const int Maxn=50;
const int Maxlen=1e6;

int K;
int A[Maxn+5];
char s[Maxlen+5];
int pos;

double Solve() {
	if(s[pos++]!='(')
		return 0;
	vector<double> num;
	char now;
	bool op=false;//false-+,true-*
	while(now=s[pos++]) {
		if(now=='\0')break;
		if(now=='(')pos--,num.push_back(Solve());
		if(now=='?')num.push_back(A[1]);
		if(now=='+')op=false;
		if(now=='*')op=true;
		if(now==')') {
			if(op==false) {
				double ret1=A[num.size()],ret2=0;
				for(int i=0;i<(int)num.size();i++)
					ret2+=num[i];
//				printf("+: %lf\n",min(ret1,ret2));
				return min(ret1,ret2);
			} else {
				int cnt=0;
				double tmp[Maxn+5];
				bool b[Maxn+5];
				double maxnum=1.0*A[num.size()]/num.size();
				for(int i=0;i<(int)num.size();i++)
					b[i]=false,tmp[i]=0;
				for(int i=0;i<(int)num.size();i++)
					if(!b[i]) {
						if(num[i]<maxnum) {
							tmp[i]=num[i];
							cnt++,b[i]=true;
							maxnum+=1.0*(maxnum-num[i])/(num.size()-cnt);
							i=-1;
							continue;
						} else tmp[i]=maxnum;
					}
				double ret=tmp[0];
				for(int i=1;i<(int)num.size();i++)
					ret*=tmp[i];
//				printf("*: %lf\n",ret);
				return ret;
			}
		}
	}
	return 0;
}

int main() {
	freopen("kvalitetni.in","r",stdin);
	freopen("kvalitetni.out","w",stdout);
	scanf("%d",&K);
	for(int i=1;i<=K;i++)
		scanf("%d",&A[i]);
	scanf("\n%s",s);
	printf("%.6lf",Solve());
	return 0;
}

Zoltan

题目翻译

COCI 2016/2017 Round #3 题解_第5张图片

分析

我们先解决第一个问题:

由于向右写,元素的相对位置是不变的;而向左写,元素的相对位置就相当于对称了一下。所以我们只要将整个数组以第一个元素为对称中心对称一下,再求一遍最长上升子序列即可获得最大的长度 M M M

那我们不对称呢?

x x x是这个最长的最长上升子序列的中间的一个元素,如下图:

COCI 2016/2017 Round #3 题解_第6张图片红色字体是元素在数组中的下标;紫圈圈住的是对称后比 x x x小的数,它们对答案有贡献;蓝圈圈住的是比 x x x大的数,它们也对答案有贡献;粉圈是紫圈圈住的数在原数组中的位置。

不难发现,若 x x x对答案有贡献,那么以 x x x为起点的最长上升子序列长度 l x 1 l_{x_1} lx1(不包括 x x x)与方案数 n x 1 n_{x_1} nx1和最长下降子序列的长度 l x 2 l_{x_2} lx2(不包括 x x x)与方案数 n x 2 n_{x_2} nx2必须满足:当 l x 1 + l x 2 + 1 = M l_{x_1}+l_{x_2}+1=M lx1+lx2+1=M时,它对答案的贡献为 n x 1 n x 2 n_{x_1}n_{x_2} nx1nx2

当我们找出所有的 x x x后,由于还有 N − M N-M NM个数没有位置放,而这 M M M个数的顺序是固定的,这就相当于这 N − M N-M NM个数随机向左或向右写,这些数对答案的贡献为 2 N − M 2^{N-M} 2NM,所以总方案数为: 2 N − M × ∑ l x 1 + l x 2 + 1 = M n x 1 n x 2 2^{N-M}\times\sum_{l_{x_1}+l_{x_2}+1=M}^{}n_{x_1}n_{x_2} 2NM×lx1+lx2+1=Mnx1nx2

由于求解这四个数需要利用的是后面的信息,所以我们倒序求解。

但若以 O ( N 2 ) O(N^2) O(N2)来求解 l x 1 , l x 2 , n x 1 , n x 2 l_{x_1},l_{x_2},n_{x_1},n_{x_2} lx1,lx2,nx1,nx2只能得到部分分,所以我们必须高效求解。

我们利用树状数组解决这个问题。这两个树状数组以数组中的数为下标,分别记录值为 x x x的数在数组中的最长上升子序列和最长下降子序列的长度和方案数的前缀和。

我们将长度和方案数绑成一个pair。由于必须找到长度和方案数的最优值,我们重新定义一下加法,每次加上另一个pair时,若原来的长度小于现在的长度,那么我们就用另外的pair代替原来的;若长度相同,则将方案数加上。

这样我们就实现了复杂度为 O ( log ⁡ 2 N ) O(\log_2 N) O(log2N)的最优长度和方案数的查询,总时间复杂度被优化至 O ( N log ⁡ 2 N ) O(N\log_2 N) O(Nlog2N)

参考代码

#include
#include
#include
using namespace std;

//typedef first len;
//typedef second num;
typedef long long ll;
typedef pair<int,ll> Pair;
const int Maxn=2e5;
const ll Mod=1e9+7;

inline ll QuickPow(ll a,int k) {
	ll ret=1;
	while(k) {
		if(k&1)ret=(ret*a)%Mod;
		a=(a*a)%Mod;
		k>>=1;
	}
	return ret;
}

int N,A[Maxn+5];
int siz;

Pair nom[Maxn+5],inv[Maxn+5];
Pair Normal[Maxn+5],Inver[Maxn+5];

Pair operator + (Pair lhs,Pair rhs) {
	if(lhs.first<rhs.first) {
		lhs.first=rhs.first;
		lhs.second=rhs.second;
	} else if(lhs.first==rhs.first)
		lhs.second=(lhs.second+rhs.second)%Mod;
	return lhs;
}
inline int lowbit(int x){return x&(-x);}
void UpdateNormal(int x,Pair val) {
	while(x<=N) {
		Normal[x]=Normal[x]+val;
		x+=lowbit(x);
	}
}
Pair SumNormal(int x) {
	Pair ret(0,0);
	while(x>0) {
		ret=ret+Normal[x];
		x-=lowbit(x);
	}
	return ret;
}

void UpdateInverse(int x,Pair val) {
	while(x>0) {
		Inver[x]=Inver[x]+val;
		x-=lowbit(x);
	}
}
Pair SumInverse(int x) {
	Pair ret(0,0);
	while(x<=N) {
		ret=ret+Inver[x];
		x+=lowbit(x);
	}
	return ret;
}

void Prepare() {
	vector<int> v;
	for(int i=1;i<=N;i++)
		v.push_back(A[i]);
	sort(v.begin(),v.end());
	v.resize(unique(v.begin(),v.end())-v.begin());
	for(int i=1;i<=N;i++)
		A[i]=lower_bound(v.begin(),v.end(),A[i])-v.begin()+1;
	siz=v.size();
}

void CalcNormal() {
	for(int i=N;i>=1;i--) {
		Pair tmp=SumNormal(A[i]-1);
		if(tmp.first==0) {
			nom[i]=make_pair(0,1);
			UpdateNormal(A[i],make_pair(1,1));
		} else {
			nom[i]=tmp;
			tmp.first++;
			UpdateNormal(A[i],tmp);
		}
	}
}
void CalcInverse() {
	for(int i=N;i>=1;i--) {
		Pair tmp=SumInverse(A[i]+1);
		if(tmp.first==0) {
			inv[i]=make_pair(0,1);
			UpdateInverse(A[i],make_pair(1,1));
		} else {
			inv[i]=tmp;
			tmp.first++;
			UpdateInverse(A[i],tmp);
		}
	}
}

int main() {
	freopen("zoltan.in","r",stdin);
	freopen("zoltan.out","w",stdout);
	scanf("%d",&N);
	for(int i=1;i<=N;i++)
		scanf("%d",&A[i]);
	Prepare();
	CalcNormal();
	CalcInverse();
	Pair ans(0,0);
	for(int i=1;i<=N;i++)
		ans=ans+make_pair(nom[i].first+inv[i].first+1,nom[i].second*inv[i].second%Mod);
	ans.second=(ans.second*QuickPow(2,N-ans.first))%Mod;
	printf("%d %lld\n",ans.first,ans.second);
	return 0;
}

Meksikanac

计算几何确实做不来啊QAQ,听说还要用什么FFT???

算了就直接把题目翻译和官方给的题解放上来就是了。。。

题目翻译

COCI 2016/2017 Round #3 题解_第7张图片

官方题解

COCI 2016/2017 Round #3 题解_第8张图片
COCI 2016/2017 Round #3 题解_第9张图片

你可能感兴趣的:(#,COCI系列比赛)