Codeforces Global Round 15

E. Colors and Intervals

题目描述

点此看题

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

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

(n,kleq 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
",ans[i].first,ans[i].second);
}

G. A Serious Referee

题目描述

点此看题

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

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

(nleq 40,kleq 10)

解法

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

不妨设原序列为排列,对于所有 (1leq sleq 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) 即可。因为这道题范围是 ( t ll),你要用 __builtin_popcountll(x),这是针对 ( t 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
",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");
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15156174.html