COCI 2016/2017 Round #5题解

COCI 2016/2017 Round #5

Tuna

题目翻译

分析

水题,按题意模拟即可。

参考代码

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

int main() {
	freopen("tuna.in","r",stdin);
	freopen("tuna.out","w",stdout);
	int N,X;
	scanf("%d %d",&N,&X);
	int ans=0;
	for(int i=1;i<=N;i++) {
		int p1,p2,p3;
		scanf("%d %d",&p1,&p2);
		if(p1<p2)swap(p1,p2);
		if(p1-p2>X) {
			scanf("%d",&p3);
			ans+=p3;
		} else ans+=p1;
	}
	printf("%d\n",ans);
	return 0;
}

Pareto

题目翻译

这题题面真涨知识。。。

分析

稍加分析可发现,我们将账户余额从大到小排序后一个一个暴力比较即可。

注意输出时不要用%lf,用%f。(听说这是一个未定义行为)

参考代码

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

typedef long long ll;
const int Maxn=3e5;

int N;
ll A[Maxn+5];
double ans1,ans2;

int main() {
	freopen("pareto.in","r",stdin);
	freopen("pareto.out","w",stdout);
	scanf("%d",&N);
	ll sum=0,tot=0;
	for(int i=1;i<=N;i++) {
		scanf("%lld",&A[i]);
		sum+=A[i];
	}
	sort(A+1,A+N+1);
	reverse(A+1,A+N+1);
	for(int i=1;i<=N;i++) {
		tot+=A[i];
		double t1,t2;
		t1=100.0*i/N,t2=100.0*tot/sum;
		if((t2-t1)>(ans2-ans1))
			ans1=t1,ans2=t2;
	}
	printf("%f\n%f\n",ans1,ans2);
	return 0;
}

Unija

题目翻译

分析

不难发现每个象限内的面积都是一样的,所以我们可以只求第一象限的面积,将这个值乘4即可得到答案。

这样原本不整齐的边变得整齐了,这样更容易计算了。

那么我们考虑离散化+线段树,似乎空间和时间都很卡。。。

只有考虑扫描了。

我们将所有矩形按 y y y值进行排序,再记录一个当前的最大值 m m m,当一个矩形的 x i x_i xi小于 m m m时,这个矩形位于之前的矩形中,否则则在此矩形之外,我们这时就需要加上这多出来的部分,并更新 m m m

参考代码

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

typedef long long ll;
const int Maxn=1e6;

struct Matrix {
	ll x,y;
}M[Maxn+5];
int N;
bool cmp(const Matrix &lhs,const Matrix &rhs) {return lhs.y>rhs.y;}

int main() {
	freopen("unija.in","r",stdin);
	freopen("unija.out","w",stdout);
	scanf("%d",&N);
	for(int i=1;i<=N;i++) {
		scanf("%lld %lld",&M[i].x,&M[i].y);
		M[i].x/=2,M[i].y/=2;
	}
	sort(M+1,M+N+1,cmp);
	ll maxx=0;
	ll ans=0;
	for(int i=1;i<=N;i++)
		if(maxx<M[i].x) {
			ans+=((M[i].x-maxx)*M[i].y);
			maxx=M[i].x;
		}
	printf("%lld\n",4*ans);
	return 0;
}

Ronald

题目翻译

分析

简要分析可知,要想得到一个完全图,那么最后一步操作中所选的点,必须是与图中任何一个其他的点没有连接,且其他的点都相互连接的。

但我们没有办法从这个结论入手。我们换个方向,从最开始选的点入手。

那么我们考虑强制 1 1 1号点为最先选的点。

接下来说明这个方法的正确性:

由于两城市之间的航线在选其中一个时都会发生改变,所以,这条航线是否存在取决于Krump的操作次数,故我们可以限定每个城市要么不选,要么只选一次。

那么对于城市 1 1 1,我们有两种选择:开始不选,开始选一次。那么对于两种选择,我们都可以检查它是否是一个完全图。对于其他任何一个城市,我们都可以通过选它来增加它到 1 1 1的航线。我们这样就可以贪心地选择所有其他的点,并最后判断是否是一个完全图即可。

还有一个结论:若答案是DA,则图中有且只有两个连通块且这两个连通块是完全的。

我没有证出来。。。

参考代码

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

const int Maxn=1000;

int N,M;
bool G[Maxn+5][Maxn+5];
bool tmp[Maxn+5][Maxn+5];
bool vis[Maxn+5];

void Filp(bool g[][Maxn+5],int u) {
	for(int i=1;i<=N;i++)
		if(i!=u)g[u][i]=g[i][u]=(!g[u][i]);
}
bool Check(bool g[][Maxn+5]) {
	for(int i=2;i<=N;i++)
		if(!g[1][i])Filp(g,i);
	for(int i=1;i<=N;i++)
		for(int j=i+1;j<=N;j++)
			if(g[i][j]==false)
				return false;
	return true;
}

int main() {
	freopen("ronald.in","r",stdin);
	freopen("ronald.out","w",stdout);
	scanf("%d %d",&N,&M);
	for(int i=1;i<=M;i++) {
		int u,v;
		scanf("%d %d",&u,&v);
		G[u][v]=G[v][u]=true;
	}
	memcpy(tmp,G,sizeof tmp);
	Filp(tmp,1);
	puts(Check(tmp)||Check(G)?"DA":"NE");
	return 0;
}

Poklon

题目翻译

分析

莫队的板子题。。。直接写就是。。。注意题目要求的是恰好两次,故在增加或删减的时候只要出现次数达到3或1就答案减一。

参考代码

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

const int Maxn=5e5;

int N,Q,A[Maxn+5];
int Block;
vector<int> a;

struct Query {
	int l,r,id;
	int ans;
};
bool cmp_query(Query lhs,Query rhs){return (lhs.l/Block==rhs.l/Block?lhs.r<rhs.r:lhs.l/Block<rhs.l/Block);}
bool cmp_getans(Query lhs,Query rhs){return lhs.id<rhs.id;}

Query q[Maxn+5];
int cnt[Maxn+5],ans;

void add(int pos) {
	cnt[A[pos]]++;
	if(cnt[A[pos]]==2)
		ans++;
	if(cnt[A[pos]]==3)
		ans--;
}
void del(int pos) {
	cnt[A[pos]]--;
	if(cnt[A[pos]]==2)
		ans++;
	if(cnt[A[pos]]==1)
		ans--;
}

int main() {
	freopen("poklon.in","r",stdin);
	freopen("poklon.out","w",stdout);
	scanf("%d %d",&N,&Q);
	Block=sqrt(N);
	for(int i=1;i<=N;i++) {
		scanf("%d",&A[i]);
		a.push_back(A[i]);
	}
	sort(a.begin(),a.end());
	a.resize(unique(a.begin(),a.end())-a.begin());
	for(int i=1;i<=N;i++)
		A[i]=lower_bound(a.begin(),a.end(),A[i])-a.begin()+1;
	for(int i=1;i<=Q;i++) {
		scanf("%d %d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+Q+1,cmp_query);
	int l=1,r=0;
	for(int i=1;i<=Q;i++) {
		while(l<q[i].l)
			del(l),l++;
		while(l>q[i].l)
			l--,add(l);
		while(r<=q[i].r)
			add(r),r++;
		while(r>q[i].r+1)
			r--,del(r);
		q[i].ans=ans;
	}
	sort(q+1,q+Q+1,cmp_getans);
	for(int i=1;i<=Q;i++)
		printf("%d\n",q[i].ans);
	return 0;
}

Strelice

题目翻译

分析

对于棋盘上的第一列的任意一个位置,我们可以暴力地处理出这条路径上的起点和到最后一列的终点的路径,并将它们都画在图上。

我们把最后一列看成一个点的话,那么,很显然的,整张图就成了一棵树。我们称此时的第一列的所有点为叶子节点。

对于每一个叶子,我们将它们从上至下地标号;对于每一个非叶子节点,我们记录下若将它涂色,那它能覆盖的叶子节点编号区间,即从这些叶子节点出发能够经过这个点,因为我们很容易知道这个棋盘上路径是不会交叉的。

但有些叶子节点是无法走到最后一列的,这些点,以及通过这些点能够走到的点,对于答案是有贡献的(因为我们要求恰好覆盖)。我们可以把这些点用来凑数。

通过以上的操作,我们将这个问题变成了一个集合覆盖问题,这个问题我们用DP解决。

s x , y s_{x,y} sx,y为选坐标为 ( x , y ) (x,y) (x,y)的点时,这个点能够覆盖的最小的叶子节点编号, t x , y t_{x,y} tx,y为选 ( x , y ) (x,y) (x,y)时能覆盖到的最大叶子节点编号。

那么定义状态 f [ i ] [ j ] f[i][j] f[i][j]为选出 j j j个节点,使这些点恰好覆盖了编号小于等于 i i i的叶子节点。

不难得出状态转移方程: f [ t x , y ] [ j + 1 ] = f [ t x , y ] [ j + 1 ] ∨ f [ s x , y ] [ j ] f[t_{x,y}][j+1]=f[t_{x,y}][j+1]\vee f[s_{x,y}][j] f[tx,y][j+1]=f[tx,y][j+1]f[sx,y][j]

这是一个求可行性的状态转移方程,我们必须在转移时记录路径。

最后找出一些能够恰好覆盖有用的叶子节点的节点,加上一些凑数的节点,凑齐 K K K个点即可。

由于我们不可能开一个map来存储这些点的所有 s , t s,t s,t与树形图,所以我们必须将坐标压成一个 S − 1 S-1 S1进制数,即 ( x , y ) (x,y) (x,y)表示成 1 + x ( S − 1 ) + y 1+x(S-1)+y 1+x(S1)+y。(用 0 0 0号点表示最后一列的所有节点)

注意由于点数最多只能涂上 R ( S − 1 ) R(S-1) R(S1)个,所以我们要特判一下。

参考代码

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

typedef pair<int,int> pii;
const int Maxsize=1e6;
const int Maxk=50;

int N,M,K;
char str[Maxsize+5];
vector<int> G[Maxsize+5];
bool leaf[Maxsize+5];

inline int ID(int x,int y){return (y==M-1?0:(1+x*(M-1)+y));}
inline pii Inv(int p){return make_pair((p-1)/(M-1),(p-1)%(M-1));}

bool vis[Maxsize+5];
int cnt;
vector<pii> pth[Maxsize+5];
int s[Maxsize+5],t[Maxsize+5];

void DFS(int u,bool in_path) {
	vis[u]=true;
	s[u]=cnt;
	if(leaf[u])cnt++;
	for(int i=0;i<(int)G[u].size();i++) {
		int v=G[u][i];
		if(vis[v])continue;
		DFS(v,in_path|leaf[u]);
	}
	t[u]=cnt;
	if(in_path==false&&u!=0&&s[u]!=t[u])
		pth[s[u]].push_back(make_pair(u,t[u]));
}

bool f[Maxsize+5][Maxk+5];
int pnt[Maxsize+5][Maxk+5];

int main() {
	freopen("strelice.in","r",stdin);
	freopen("strelice.out","w",stdout);
	scanf("%d %d %d",&N,&M,&K);
	if(N*(M-1)<K) {
		puts("-1");
		return 0;
	}
	for(int i=0;i<N;i++) {
		scanf("%s",str);
		for(int j=0;j<M-1;j++) {
			int pos=ID(i,j);
			if(j==0)leaf[pos]=true;
			char ch=str[j];
			if(ch=='R')G[ID(i,j+1)].push_back(pos);
			if(ch=='L')G[ID(i,j-1)].push_back(pos);
			if(ch=='D')G[ID(i+1,j)].push_back(pos);
			if(ch=='U')G[ID(i-1,j)].push_back(pos);
		}
	}
	DFS(0,false);
	vector<pair<int,int> > unused;
	for(int i=1;i<=N*(M-1);i++) {
		if(vis[i]==false)unused.push_back(Inv(i));
		else if(s[i]==t[i])unused.push_back(Inv(i));
	}
	f[0][0]=true;
	for(int i=0;i<cnt;i++)
		for(int tmp=0;tmp<(int)pth[i].size();tmp++) {
			pii j=pth[i][tmp];
			for(int k=0;k<K;k++)
				if(f[i][k]) {
					f[j.second][k+1]=true;
					pnt[j.second][k+1]=j.first;
				}
		}
	for(int i=0;i<=K;i++)
		if(f[cnt][i]&&i+(int)unused.size()>=K) {
			vector<pii> ans;
			for(int j=cnt;j;) {
				ans.push_back(Inv(pnt[j][i]));
				j=s[pnt[j][i]],i--;
			}
			while((int)ans.size()<K) {
				ans.push_back(unused.back());
				unused.pop_back();
			}
			for(int j=0;j<(int)ans.size();j++)
				printf("%d %d\n",ans[j].first+1,ans[j].second+1);
			return 0;
		}
	puts("-1");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值