solutions
題面loj#542
對我來說,這或許已經超出了我的能力,我,只能看題解
不知道我寫完這一篇題解之後,會不會對我的構造題有一點點的幫助
讓我在這類題的解決上能過有一些提升
直接說明白,這就是一個構造題
這個很好判斷,因為有SPJ,並且方案有很多,隨便輸出就可以
開頭是K,結尾是K,長度也是K
總結一下,我們一定可以將這個序列劃分成K個序列,如果你劃分的更多的話,我完全可以將多出來的合併一下
並且,我們的起點和終點,一定是前K個和後K個可以被K整除的數,
如果不是,那肯定前面或者後面有剩餘的,直接換一下起點終點就行了
那麼我們就可以著手去做這道題了,
我們會有兩種情況,其餘更多的情況都可以直接轉化成這兩種當中的一種
我們將左端點和右端點,按照位置大小從小到大,分別存入兩個陣列中
第一種,每一個左端點都對應相應的右端點,就是說左端點和右端點在各自陣列中的排名是相同的(具體看程式碼ckfi)
如果你當前左端點對應的是後面的右端點,那麼後面一定有一個左端點對應當前應該對應的右端點,我就可以開始轉化了
第二種,第一種中有些序列不能覆蓋完全當前左端點到下一個左端點的區間內的點,
那麼就讓第一個左端點和最後一個右端點空出來,每一個左端點都匹配它前一個右端點
最後用第一個左端點和最後一個右端點把沒有加上的點全部都加上去就好了
於是我們只需要對兩種情況分別判斷就好了
這兩種情況中只能滿足一個,這個很顯然吧,因為如果第二個滿足的話,第一個肯定不夠啊
code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e6+5;
int n,k,a[N];
void F(){printf("No\n");}
int l[N],lc,r[N],rc,p[N];
void pri(int &it,int tot){while(tot&&it<=n)if(a[it++])a[it-1]=0,printf("%d ",it-1),tot--;}
bool chfi(){
int it=0,las,nf;
for(re i=1;i<k;i++){
it+=k-2;while(it<p[l[i+1]])it+=k;
if(it>p[r[i]])return false;
}
printf("Yes\n%d\n",k);
it=0;las=0;nf=2;
for(re i=1;i<k;i++){
las=it;it+=k-2;while(it<p[l[i+1]])it+=k;
printf("%d %d ",it-las+2,l[i]);
pri(nf,it-las);printf("%d\n",r[i]);
}
printf("%d %d ",p[n]-it+2,l[k]);
pri(nf,p[n]-it);printf("%d\n",r[k]);
return true;
}
bool chse(){
int it=0,nf;
for(re i=2;i<=k;i++){
it=max(it,p[l[i]]);it+=k-2;
if(it>p[r[i-1]])return false;
}
printf("Yes\n%d\n",k);nf=0;
for(re i=2;i<=k;i++){
while(p[nf]<=p[l[i]])nf++;
printf("%d %d ",k,l[i]);
pri(nf,k-2);printf("%d\n",r[i-1]);
}
printf("%d %d ",p[n]-(k-2)*(k-1)+2,l[1]);
nf=2;pri(nf,p[n]-(k-2)*(k-1));printf("%d\n",r[k]);
return true;
}
signed main(){
int o;scanf("%d",&o);
scanf("%d%d",&n,&k);
for(re i=1;i<=n;i++)scanf("%d",&a[i]),a[i]=(a[i]%k!=0);
if(n%k||n<1ll*k*k||a[1]||a[n]){F();return 0;}
for(re i=1;i<=n;i++)if(!a[i])l[++lc]=i;
for(re i=n;i>=1;i--)if(!a[i])r[++rc]=i;
if(lc<k||rc<k||l[k]>=r[k]){F();return 0;}
lc=rc=k;reverse(r+1,r+k+1);
for(re i=l[k]+1;i<r[1];i++)a[i]=1;
for(re i=1;i<=n;i++)p[i]=p[i-1]+a[i];
if(chfi()||chse());else F();
}