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\) 个:
然后就可以开始卡常了,判断一个 \(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");
}