Codeforces Global Round 15

E. Colors and Intervals

题目描述

点此看题

\(n\) 种颜色,每种颜色恰好有 \(k\) 个,他们排成一个长度为 \(n\times k\) 的颜色序列 \(a\)

每种颜色需要选两个端点,这两个点会构成一个区间,试构造方案使得每个点最多被覆盖 \(\lceil\frac{n}{k-1}\rceil\) 次。

\(n,k\leq 100\)

解法

不难转化题意:每 \(k-1\) 个颜色分成一组,要求每一组的区间最多覆盖每个点一次。

再观察每种颜色 \(k\) 个端点,那么 \(k-2\) 个端点会被浪费,可以猜想每种颜色的匹配最多浪费其他颜色的一个端点。

我们取出这一组所有有关的端点,然后从左往右扫,如果这种颜色没有左端点那么记录左端点,如果这种颜色有左端点那么把这个位置当成右端点,然后把其它颜色的左端点往右移直接不交,不难发现其他颜色最多移动一次,因为如果移动多次那么先匹配的就是它了。

总结

和次数相关的构造题一定要找次数的现实意义,除法可以联想到分组,当然如果题目给的是具体数字我们要猜这个数字是怎么来的。

其实我觉得第二步构造是很难的,但是从感觉上说我们想把距离近的点匹配到一起,所以本题的顺序扫描法是很有可能的,我一开始想的时候想的是归纳构造法但是走不通,有时候整体和局部的构造方法都值得去想一想。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 100005;
#define pii pair<int,int>
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,a[M],b[M],nxt[M];pii ans[M];
signed main()
{
	n=read();k=read();
	for(int i=1;i<=n*k;i++)
		a[i]=read();
	for(int l=1;l<=n;l+=k-1)
	{
		int r=min(n,l+k-2);vector<int> g;
		for(int i=1;i<=n*k;i++)
			if(l<=a[i] && a[i]<=r)
				g.push_back(i);
		for(int i=0;i<g.size();i++)
		{
			if(b[a[g[i]]]) nxt[b[a[g[i]]]]=g[i];
			b[a[g[i]]]=g[i];
		}
		for(int i=l;i<=r;i++) b[i]=0;
		for(int i=0;i<g.size();i++)
		{
			if(!ans[a[g[i]]].first && b[a[g[i]]] && b[a[g[i]]]!=g[i])
			{
				ans[a[g[i]]]=make_pair(b[a[g[i]]],g[i]);
				for(int j=l;j<=r;j++)
					if(b[j]<=g[i]) b[j]=nxt[b[j]];
			}
			else b[a[g[i]]]=g[i];
		}
	}
	for(int i=1;i<=n;i++)
		printf("%d %d\n",ans[i].first,ans[i].second);
}

G. A Serious Referee

题目描述

点此看题

哥哥 \(\tt zxy\) 设计了一种排序方法,这个排序操作有 \(k\) 步,每次选择一个子序列将其内部升序排列。

天下第一妹的 \(\tt jzm\) 要审核这个排序方法,所以她想知道这种排序方法适用于所有长度为 \(n\) 的序列。

\(n\leq 40,k\leq 10\)

解法

\(\tt zero-one\ principle\):对于任何的排序方式,长度为 \(n\) 的序列能排好序当且仅当长度为 \(n\)\(0/1\) 序列能排好序,必要性显然,下证充分性:

不妨设原序列为排列,对于所有 \(1\leq s\leq n\),我们把小于等于 \(s\) 的元素设置为 \(0\),大于 \(s\) 的元素设置为 \(1\),然后把这个 \(0/1\) 序列排序,我们可以得到下列信息:

  • 排序后小于等于 \(s\) 的元素在前 \(s\) 个位置上。

那么可以依次推出最小的元素在第一个位置,次小的元素在第二个位置\(...\)充分性得证

那么我们只需要考虑对所有 \(01\) 序列是否能排好序即可,暴力枚举 \(O(2^{40})\) 受不了,而折半搜索又不行。

现在排序之后状态量会减少,那么我们按操作来考虑,考虑到第 \(i\) 个操作时只记录前 \(i\) 个操作涉及到的位置的状态。我们考虑怎么扩展状态,每次我们考虑新增的位置的状态,但是因为要排序我们只需要枚举新增的位置有多少个 \(1\) 就可以得到新的状态了。

因为新增位置的总数是 \(n\),而最多新增 \(k\) 次,设每次新增了 \(s_i\) 个:

\[(s_1+1)(s_2+1)...(s_k+1)\leq (\frac{n+k}{k})^k=O(5^k) \]

然后就可以开始卡常了,判断一个 \(01\) 序列是否排好序的方法:找出 \(0\) 的个数,然后看前这么多位是否全是 \(0\) 即可。因为这道题范围是 \(\tt ll\),你要用 __builtin_popcountll(x),这是针对 \(\tt ll\) 统计二进制有多少个 \(1\) 的函数。

总结

像这种有很奇怪数据范围的题,一定注意减少状态量即可,思考按什么顺序考虑状态会少一些。

#pragma GCC optimize(2)
#include <cstdio>
#include <cassert>
#include <algorithm>
using namespace std;
const int M = 105;
const int N = 10000005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,b[M],x[M],w[N],h[N],st[M];
signed main()
{
	n=read();k=read();w[m=1]=0;
	for(int i=1;i<=n;i++)
		st[i]=st[i-1]|(1ll<<i-1);
	for(int i=1;i<=k;i++)
	{
		int p=read();
		for(int j=1;j<=p;j++)
		{
			x[j]=read()-1;
			b[i]|=1ll<<x[j];
		}
		int s=b[i-1]&b[i],t=m;m=0;
		int sz=__builtin_popcountll(b[i]-s);
		for(int j=1;j<=t;j++) h[j]=w[j];
		for(int j=1;j<=t;j++)
		{
			int cnt=__builtin_popcountll(s&h[j]);
			int st=h[j]^(s&h[j]);
			//printf("%d %d\n",cnt,sz);
			for(int k=0;k<cnt;k++)
				st|=(1ll<<x[p-k]);
			for(int k=cnt;k<=sz+cnt;k++)
				w[++m]=st,st|=(1ll<<x[p-k]);
		}
		b[i]|=b[i-1];
	}
	if(n==1)
	{
		puts("ACCEPTED");
		return 0;
	}
	if(b[k]!=(1ll<<n)-1)
	{
		puts("REJECTED");
		return 0;
	}
	for(int i=1;i<=m;i++)
	{
		int sz=n-__builtin_popcountll(w[i]);
		if(w[i]&st[sz])
		{
			puts("REJECTED");
			return 0;
		}
	}
	puts("ACCEPTED");
}
posted @ 2021-08-18 11:53  C202044zxy  阅读(82)  评论(0编辑  收藏  举报