2019.8.3 [HZOI]NOIP模拟测试12 C. 分组
全场比赛题解:https://pan.baidu.com/s/1eSAMuXk
刚看这题觉得很难,于是数据点分治
k只有1和2两种,分别讨论:
- k=1:
根据人类直觉,不难想到一种贪心策略:从前往后扫,若扫到的数能加入当前这段就加入,否则再开一段新的。
于是你就WA了...
题目要求字典序最小,而我们的策略会让当前段尽可能长,所以划分点会靠后。例如:1 2 3 4 5,可以分为{1},{2,3,4},{5}三段,而我们的策略得到的答案为{1,2},{3,4},{5}。
类似《菜肴制作》那道题,既然正向贪心不对,考虑反向贪心。
可以从后往前扫描,仍按贪心策略,可以保证后面的段尽量长,即划分点尽量靠左。官方题解证明了这种策略的正确性。
现在我们的问题是如何快速判定一个数能不能加入当前段落,即对于一个数b,判断当前段是否存在一个数a,满足\(a+b=x^2\)。发现\(\sqrt{2*\max(a_i)}\)只有520左右,可以每次枚举x,判断\(x^2-b\)是否存在。
复杂度\(O(n\sqrt n)\) - k=2:仍可以按上述策略从后往前扫,每次相当于判断当前段加入一个数之后能否划分为两个集合,使每个集合内任意两数之和都不是完全平方数。类似《魔术球》那道题,就是二分图判定。二分图判定用染色法复杂度较高,可以用边带权/扩展域并查集判定二分图(类似《关押罪犯》)。
根据k=1的做法,我们还是想到按数值合并,但这时会出现一种特殊情况:当序列为2、7时,2和7可以划分在两个集合中;但当序列中出现两个2,这两个2需要划分在不同集合,再出现一个7就不能划分在这两个集合中了。所以扫描的时候需要特判。
这里并查集只写了路径压缩,没有按秩合并,所以复杂度多了个log。
Code:
#include <bits/stdc++.h>
using namespace std;
const int N=131075;
int table[600]={0,4,9,16,25,36,49,64,81,100,121,144,169,196,225,256,289,324,361,400,441,484,529,576,625,676,729,784,841,900,961,1024,1089,1156,1225,1296,1369,1444,1521,1600,1681,1764,1849,1936,2025,2116,2209,2304,2401,2500,2601,2704,2809,2916,3025,3136,3249,3364,3481,3600,3721,3844,3969,4096,4225,4356,4489,4624,4761,4900,5041,5184,5329,5476,5625,5776,5929,6084,6241,6400,6561,6724,6889,7056,7225,7396,7569,7744,7921,8100,8281,8464,8649,8836,9025,9216,9409,9604,9801,10000,10201,10404,10609,10816,11025,11236,11449,11664,11881,12100,12321,12544,12769,12996,13225,13456,13689,13924,14161,14400,14641,14884,15129,15376,15625,15876,16129,16384,16641,16900,17161,17424,17689,17956,18225,18496,18769,19044,19321,19600,19881,20164,20449,20736,21025,21316,21609,21904,22201,22500,22801,23104,23409,23716,24025,24336,24649,24964,25281,25600,25921,26244,26569,26896,27225,27556,27889,28224,28561,28900,29241,29584,29929,30276,30625,30976,31329,31684,32041,32400,32761,33124,33489,33856,34225,34596,34969,35344,35721,36100,36481,36864,37249,37636,38025,38416,38809,39204,39601,40000,40401,40804,41209,41616,42025,42436,42849,43264,43681,44100,44521,44944,45369,45796,46225,46656,47089,47524,47961,48400,48841,49284,49729,50176,50625,51076,51529,51984,52441,52900,53361,53824,54289,54756,55225,55696,56169,56644,57121,57600,58081,58564,59049,59536,60025,60516,61009,61504,62001,62500,63001,63504,64009,64516,65025,65536,66049,66564,67081,67600,68121,68644,69169,69696,70225,70756,71289,71824,72361,72900,73441,73984,74529,75076,75625,76176,76729,77284,77841,78400,78961,79524,80089,80656,81225,81796,82369,82944,83521,84100,84681,85264,85849,86436,87025,87616,88209,88804,89401,90000,90601,91204,91809,92416,93025,93636,94249,94864,95481,96100,96721,97344,97969,98596,99225,99856,100489,101124,101761,102400,103041,103684,104329,104976,105625,106276,106929,107584,108241,108900,109561,110224,110889,111556,112225,112896,113569,114244,114921,115600,116281,116964,117649,118336,119025,119716,120409,121104,121801,122500,123201,123904,124609,125316,126025,126736,127449,128164,128881,129600,130321,131044,131769,132496,133225,133956,134689,135424,136161,136900,137641,138384,139129,139876,140625,141376,142129,142884,143641,144400,145161,145924,146689,147456,148225,148996,149769,150544,151321,152100,152881,153664,154449,155236,156025,156816,157609,158404,159201,160000,160801,161604,162409,163216,164025,164836,165649,166464,167281,168100,168921,169744,170569,171396,172225,173056,173889,174724,175561,176400,177241,178084,178929,179776,180625,181476,182329,183184,184041,184900,185761,186624,187489,188356,189225,190096,190969,191844,192721,193600,194481,195364,196249,197136,198025,198916,199809,200704,201601,202500,203401,204304,205209,206116,207025,207936,208849,209764,210681,211600,212521,213444,214369,215296,216225,217156,218089,219024,219961,220900,221841,222784,223729,224676,225625,226576,227529,228484,229441,230400,231361,232324,233289,234256,235225,236196,237169,238144,239121,240100,241081,242064,243049,244036,245025,246016,247009,248004,249001,250000,251001,252004,253009,254016,255025,256036,257049,258064,259081,260100,261121,262144,263169,264196,265225,266256,267289,268324,269361,270400};
int n,k,mx,tot,ans[N],a[N];
bool vis[N],dvis[N],issqu[3*N];
void In(int &num){
register char c=getchar();
for(num=0;!isdigit(c);c=getchar());
for(;isdigit(c);num=num*10+c-48,c=getchar());
}
namespace solve1{
inline bool check(int num){//判断num能否加入当前段
for(int i=1;i<=520;++i){
if(table[i]<=num) continue;
if(table[i]-num>mx) break;
if(vis[table[i]-num]) return true;
}
return false;
}
void solve(){
for(int i=n,last=n;i>=2;--i){
vis[a[i]]=true;
mx=max(mx,a[i]);
if(check(a[i-1])){//a[i-1]不能加入当前段
mx=0;
for(int j=last;j>=i;--j) vis[a[j]]=false;//清空数组
ans[++tot]=i-1;//新开一段
last=i-1;
}
}
}
}
namespace solve2{
int fa[3*N];
int Find(int x){
return fa[x]==x?x:fa[x]=Find(fa[x]);
}
bool check(int u,int v){//u和v已经冲突 判断它们能否分到不同集合
int t1=Find(u),t2=Find(u+N);
int s1=Find(v),s2=Find(v+N);
if(t1==s1||t2==s2) return false;
fa[t1]=s2;fa[t2]=s1;
return true;
}
void solve(){
for(int i=1;i*i<=2*N;++i) issqu[i*i]=true;
for(int i=1;i<=2*N;++i) fa[i]=i;
for(int i=n,last=n;i;--i){
if(vis[a[i]]){//出现过
if(issqu[2*a[i]]){
if(dvis[a[i]]) goto fail;//已经出现过两次
for(int j=1;j<=520;++j){
if(table[j]<=a[i]) continue;
if(table[j]-a[i]>mx) break;
if(vis[table[j]-a[i]]&&table[j]!=2*a[i]) goto fail;
}
dvis[a[i]]=true;//标记第二次出现
}
}
else{//没出现过
for(int j=1;j<=520;++j){
if(table[j]<=a[i]) continue;
if(table[j]-a[i]>mx) break;
if(vis[table[j]-a[i]]&&(dvis[table[j]-a[i]]||(!check(a[i],table[j]-a[i])))) goto fail;
}
vis[a[i]]=true;
}
mx=max(mx,a[i]);
continue;
fail:
mx=a[i];
ans[++tot]=i;
for(int j=last;j>=i+1;--j) fa[a[j]]=a[j],fa[a[j]+N]=a[j]+N,vis[a[j]]=dvis[a[j]]=false;
fa[a[i]]=a[i],fa[a[i]+N]=a[i]+N;vis[a[i]]=true;//注意这里 当前段的右端点是i 需要重新标记
last=i;
}
}
}
int main(){
In(n);In(k);
for(int i=1;i<=n;++i) In(a[i]);
if(k==1) solve1::solve();
else solve2::solve();
printf("%d\n",tot+1);
for(int i=tot;i;--i) printf("%d ",ans[i]);
putchar('\n');
return 0;
}