2019-2020 ICPC Southeastern European Regional Programming Contest (SEERC 2019)
2019-2020 ICPC Southeastern European Regional Programming Contest (SEERC 2019)
比賽情況
我們一共過了4道題,DGIJ
本場貢獻:et3_tsy :D,J題提供關鍵思路,嘗試寫B,case過了,賽後發現就差簡單排序,差點過了
1427314831a:過了D,G
Ryker0923 :過了I, 實現J,D提供關鍵思路
罰時:1427314831a產生了所有罰時,不過D的罰時比想象中少(特例確實多)
比賽總結
本場我認為考驗了隊伍構造特例的能力
比如說D題,我們看出了當一個字元出現了n-2次,另一個出現了2次,一定不行的情況,但是 \(aabb\)
比如說B題,有一組樣例(由 wmxwmx 提供)
2 100 100
101 1 1 1
99 1 1 1
就比較坑爹了,導致幾隻隊伍一直在WA5
以及博弈題卡掉一些假演算法需要一些針對性比較強的資料
注意線段樹開四倍空間
使用*之前一定要注意相乘的兩個數會不會爆int
需要注意到一些特殊條件
圖論題給出了n為奇數的條件,一開始沒有注意到
注意到之後五分鐘就A了
同時要注意猜結論,證明要花費很多時間
猜結論很可能就過了
部分題解
B.揹包DP
這道題目的意思就是說,一個人,他要升兩個級別,等級1需要經驗是S1,他需要升等級2的經驗是S2。接下來給定N個探索的任務,然後每一個探索任務給定4個值ti,xi,ri和yi。ti,xi表示在等級1時該任務的時間花費和經驗,ri,yi表示在等級2時該任務的時間花費和經驗。
它需要完成探索來升級,升級第一級的經驗如果溢位,它會累積到第2個等級,每一個任務做完之後他就不能重複再做了,然後現在就要求他要滿足這個升級之後最短的時間是多少。
這是經典的揹包問題,但是這個題目的核心關鍵點就在於它第二輪與第一輪的物品狀態不同了
那麼我應該考慮的是,對於每一個物品,我拿它的時候當前狀態是什麼。
我們記 \(st[fir][sec]\) 為當前狀態,\(fir\)是指當前狀態第一級經驗值的多少,\(sec\)是指第二級經驗值的多少
依據題目範圍,\(fir\) 至多為\(s1+s2\),\(sec\) 至多為 \(500\)
如果我現在是等級1,把它當等級2拿了,那麼本方案就相當於是將拿的先後順序交換了一下,依然合法。
但是如果我當前已經達到等級2了,就不能再拿等級1的東西,否則非法。
那麼我們就可以產生一個很簡單的狀態轉移過程了,線性列舉各個物品即可
但是
2 100 100
101 1 1 1
99 1 1 1
這組資料會WA
因為掃完101之後,我們沒有辦法再去要99了,因為我們上面也說它非法了。
這裡的問題在於,在取101之前,99這一狀態的缺失,兩個物體是存在先後關係的
我們正確的處理應該是對其進行一個簡單的排序,使較小的靠前,就不會缺少一些前繼狀態了
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3fLL
ll read()
{
ll s=0,m=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')m=1;ch=getchar();}
while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return m?-s:s;
}
ll min(ll a,ll b)
{
return a<b?a:b;
}
//ll
ll st[1005][505]; //st[fir][sec]
ll tmp[1005][505];
int x[1005],t[1005],y[1005],r[1005];
int n,s1,s2;
int main()
{
ll ans=INF;
n=read(),s1=read(),s2=read();
memset(st,INF,sizeof(st));
memset(tmp,INF,sizeof(tmp));
st[0][0]=0;
for(int i=1;i<=n;i++)
{
x[i]=read(),t[i]=read(),y[i]=read(),r[i]=read();
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(x[i]>x[j])
{
swap(x[i],x[j]);
swap(t[i],t[j]);
swap(y[i],y[j]);
swap(r[i],r[j]);
}
}
}
for(int k=1;k<=n;k++)
{
for(int i=0;i<=(s1+s2);i++)
{
for(int j=0;j<=500;j++)
{
tmp[i][j]=min(tmp[i][j],st[i][j]);
int fir=i+x[k];
int sec=j;
if(i<s1)
{
if(fir>=s1&&fir+sec>=s1+s2)ans=min(ans,st[i][j]+t[k]);
if(fir<=s1+s2&&sec<=500)tmp[fir][sec]=min(tmp[fir][sec],st[i][j]+t[k]);
}
fir=i;
sec=j+y[k];
if(fir>=s1&&fir+sec>=s1+s2)ans=min(ans,st[i][j]+r[k]);
if(fir<=s1+s2&&sec<=500)tmp[fir][sec]=min(tmp[fir][sec],st[i][j]+r[k]);
}
}
for(int i=0;i<=(s1+s2);i++)
{
for(int j=0;j<=500;j++)
{
st[i][j]=tmp[i][j];tmp[i][j]=INF;
}
st[0][0]=0;
}
}
if(ans==INF)cout<<-1<<endl;
else cout<<ans<<endl;
return 0;
}
C.互動題
題意:
你有30次互動機會,讓你還原一個大小為n的陣列,n最大到250,陣列中每個數不同
每次互動有兩種方案,一是具體問某個位置的數。
二是,問具體k個位置,這k個位置對應的數,兩兩做差的絕對值的結果
題解:
既然每個數不同,說明極差一定,那麼我們通過縮短區段並呼叫方案2查詢,發現上界或是下界
再進行二進位制分組,2的k次方,如果能與某個數非零,那麼把它歸成一類,這一類方案2查一次,在加上上界或下界的那個元素再查方案2一次,兩次求個差集,就可以這一類中,每個值與界值的絕對值
由於每個位置在二進位制下拆解不一樣,所以,通過拆分下的每個集合的交集,就可以還原每個位置對應的數了。
這題體現了互動題的兩種思想:
1.以小見大,從最有特徵的入手,比如最大最小這樣的界限,再進一步推別的
2.高效分組。這個分組查詢的思想,很重要,尤其是二進位制按位分組。
這題又一次強化了我對set的理解:
1.multiset 如果要清處某個具體值,如果全清,則是erase即可,如果只刪一個,要erase(find(val)),注意區分
2.求交集,並集,差集,儘量不要用set_union,set_intersection這一類的函式,一方面慢,一方面接收容器只能掏出個vector,麻煩的很
#include<bits/stdc++.h>
using namespace std;
#define maxn 305
int n,x,pos;
int a[maxn],b[maxn];
multiset<int>s[8];
set<int>num;
queue<int>q;
void findpos()
{
int maxx=-1;
int l=1,r=n;
while(l+1<r)
{
int curmax=-1,mid=(maxx==-1?pos:(l+r)/2);
cout<<"2 ";
cout<<mid<<" ";
for(int i=1;i<mid;i++)
cout<<i<<" ";
cout<<mid;
cout<<"\n";
fflush(stdout);
for(int i=0;i<mid*(mid-1)/2;i++)
{
int x;
cin>>x;
curmax=max(curmax,x);
}
if(maxx==-1)
{
maxx=curmax;
continue;
}
else if(maxx!=curmax)
{
l=mid;
}
else
{
r=mid;
}
}
pos=r;
}
void dealset(int bit)
{
int bit2=(1<<bit),cnt=0,p=0;
for(int i=1;i<=n;i++)
{
if((i&bit2)==0&&i!=pos)continue;
cnt++;
}
if(cnt>=2)
{
cout<<"2 ";cout<<cnt;
for(int i=1;i<=n;i++)
{
if((i&bit2)==0&&i!=pos)continue;
cout<<" "<<i;
}
cout<<"\n";
fflush(stdout);
}
for(int i=0;i<cnt*(cnt-1)/2;i++)
{
cin>>x;s[bit].insert(x);
}
cnt=0,p=0;
for(int i=1;i<=n;i++)
{
if((i&bit2)==0||i==pos)continue;
cnt++;
}
if(cnt>=2)
{
cout<<"2 ";cout<<cnt;
for(int i=1;i<=n;i++)
{
if(i==pos||(i&bit2)==0)continue;
cout<<" "<<i;
}
cout<<"\n";
fflush(stdout);
}
for(int i=0;i<cnt*(cnt-1)/2;i++)
{
cin>>x;s[bit].erase(s[bit].find(x));
}
for(int x:s[bit])
{
num.insert(x);
}
}
void turnbtoa()
{
for(int x:num)
{
int curpos=0;
for(int i=0;i<8;i++)
{
if(s[i].count(x))
{
curpos+=(1<<i);
}
}
b[curpos]=x;
}
for(int i=2;i<=n;i++)
{
if(i==pos)continue;
a[i]=a[pos];
if(a[1]>a[pos])a[i]+=b[i];
else a[i]-=b[i];
}
}
int main()
{
cin>>n;
pos=n;
if(n<=30)
{
for(int i=1;i<=n;i++)
{
cout<<"1 "<<i<<"\n";
fflush(stdout);
cin>>a[i];
}
cout<<"3 ";
for(int i=1;i<n;i++)cout<<a[i]<<" ";cout<<a[n];
return 0;
}
findpos();
cout<<"1 "<<pos<<"\n";
fflush(stdout);
cin>>a[pos];
cout<<"1 1"<<"\n";
fflush(stdout);
cin>>a[1];
for(int i=0;i<8;i++)
{
if(n>=(1<<i))dealset(i);
}
turnbtoa();
cout<<"3 ";
for(int i=1;i<n;i++)
cout<<a[i]<<" ";
cout<<a[n];
fflush(stdout);
return 0;
}
D.分類討論
#include<bits/stdc++.h>
using namespace std;
string s;
int a[1000];
int main()
{
cin>>s;
int n=s.size();
if(n==2)
{
if(s[0]==s[1])cout<<"NO";
else
{
cout<<"YES\n";
cout<<s;
}
return 0;
}
for(int i=0;i<s.size();i++)
a[s[i]-'a']++;
int bj=-1,maxn=0;
for(int i=0;i<=25;i++)
{
if(a[i]>maxn)
{
bj=i,maxn=a[i];
}
}
if(n==4)
{
if(a[bj]>=3)cout<<"NO";
else
{
cout<<"YES\n";
for(int i=0;i<=25;i++)
{
while(a[i])
{
cout<<char(i+'a');a[i]--;
}
}
}
return 0;
}
if(a[bj]>=n-1)
{
cout<<"NO\n";
return 0;
}
if(a[bj]==n-2)
{
for(int i=0;i<=25;i++)
{
if(a[i]==2&&i!=bj)
{
cout<<"NO";
return 0;
}
}
cout<<"YES\n";
for(int i=1;i<=n/2;i++)
cout<<char(bj+'a');
int bj1;
for(int i=0;i<=25;i++)
if(a[i]==1)bj1=i;
for(int i=0;i<=25;i++)
{
if(a[i]==1)
{
cout<<char('a'+i);
break;}
}
for(int i=1;i<=n/2-2;i++)
cout<<char('a'+bj);
cout<<char('a'+bj1);
return 0;
}
if(a[bj]>n/2)
{
cout<<"YES\n";
for(int i=1;i<=n/2;i++)
cout<<char('a'+bj);
for(int i=0;i<=25;i++)
{
if(a[i]!=0&&i!=bj)
{
cout<<char('a'+i);
a[i]--;
break;
}
}
for(int i=1;i<=a[bj]-n/2;i++)
cout<<char('a'+bj);
for(int i=0;i<=25;i++)
{
while(a[i]&&i!=bj)
{
cout<<char('a'+i);a[i]--;
}
}
return 0;
}
if(a[bj]<=n/2)
{
cout<<"YES\n";
for(int i=0;i<=25;i++)
{
while(a[i])
{
cout<<char('a'+i);a[i]--;
}
}
}
return 0;
}
/*
4
2 14 7 14
5 10 9 22
*/
I.猜結論
#include<bits/stdc++.h>
using namespace std;
int n,a[10010],b[10010],c[10010];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
cin>>b[i];
for(int i=1;i<=n;i++)
c[i]=0x3f3f3f3f;
sort(a+1,a+n+1);
sort(b+1,b+n+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
c[i]=min(c[i],abs(a[i]-b[j]));
int maxn=0;
for(int i=1;i<=n;i++)
maxn=max(maxn,c[i]);
cout<<maxn;
return 0;
}
J.圖論
題意就是說給定一個無向完全圖,圖的點數是奇數,然後給定每一條邊的權值。你現在要進行一個把它拆成若干個環的操作,每一個環上你現在要統計相鄰兩條邊權值最大值的和,然後使得這些環的和加起來,求最小是多少。
其實我們要想到一個問題,既然圖的點數是奇數,又是無向完全圖,那麼對於任意點而言,它必定一入一出,那麼這條入邊與出邊在環上一定相鄰,這也就產生了一個MAX值,那麼我們對於每個點而言,只要把它的邊兩兩配對,使得單點產生的MAX值最小即可。
而什麼時候產生的MAX值最小嘞?對於單點,自小到大排序即可,每隔二取大者即可。程式碼奇短,很考察思維。
#include<bits/stdc++.h>
using namespace std;
int ma[1010][1010];
void add(int x,int y,int z)
{
ma[x][y]=ma[y][x]=z;
}
int n;
int f[1010];
long long sum;
int main()
{
cin>>n;
int x,y,z;
for(int i=1;i<=n*(n-1)/2;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for(int i=1;i<=n;i++)
ma[i][i]=0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
f[j]=ma[i][j];
sort(f+1,f+n+1);
for(int j=2;j<=n;j+=2)
sum+=1ll*f[j];
}
cout<<sum;
return 0;
}
F.博弈
樹上博弈
題意:給定一個有根樹,根為1,兩人每輪操作,第一次操作選擇樹上一個點,放置棋子,接下來每次操作,選擇沒被棋子放置過的且是當前棋子所在格子的孩子或者祖宗的點,把棋子放置到該位置。
思路:
首先先提供一種錯誤思路(這是et3_tsy的假思路):
我們假設對於一個樹而言,考慮它所有子樹的必勝必敗態。
1)如果所有子樹均是必敗態,那麼先手可以選擇父節點,那麼對於後手而言,他一定會進入的其中一個子樹並且沒有辦法再到其他子樹中,故對於先手而言,這是必勝態。
2)如果所有子樹裡至少有兩個必勝態,先手可以選擇其中一個必勝的子樹,那麼後手無論如何一定會走到父節點,那麼先手再選另一個必勝子樹。
對應程式碼:
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define max(a,b) (a>b ? a:b)
#define min(a,b) (a<b ? a:b)
#define swap(a,b) (a^=b^=a^=b)
#define maxn 105000
#define minn -105
#define ll long long int
#define ull unsigned long long int
#define uint unsigned int
inline int read()
{
int ans=0;
char last=' ',ch=getchar();
while(ch<'0'|ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans;
return ans;
}
int n;
vector<int>node[maxn];
bool dfs(int cur,int fa)
{
int cntgood=0,cntbad=0,cnt=0;
bool ok=0;
for(int k:node[cur])
{
if(fa==k)continue;
cnt++;
if(dfs(k,cur))cntgood++;
else cntbad++;
}
if(cntbad==cnt)ok=1;
if(cntgood>=2)ok=1;
if(cnt==0)ok=1;
//if(cur==2)cout<<ok<<endl;
return ok;
}
int main()
{
cin>>n;
for(int i=0;i<n-1;i++)
{
int a,b;
cin>>a>>b;
node[a].push_back(b);
node[b].push_back(a);
}
if(dfs(1,1))cout<<"Alice\n";
else cout<<"Bob\n";
return 0;
}
以上程式碼看似很對,但是下面這組就是錯的
感謝隔壁大神提供一組樣例(又由 wmxwmx 提供)
7
1 2
2 3
3 4
4 5
4 6
4 7
很顯然在博弈邏輯裡面,這種子樹遞進的邏輯是錯誤的。也就是說,博弈過程會反覆橫跳,考慮層級關係在本題不適用。
正解思路:
我們應該考慮將樹上的點轉化成圖,如果兩點相互可達則建立一條無向邊。
那麼這就是一般圖最大匹配問題了。
如果最大匹配是完美匹配,那麼一定能夠使得兩兩配對,則後手一定存在一種應對方案,後手必勝否則先手必勝。
那麼一般圖在這道題中會T,其實只要在樹上進行Dp即可,具體實現可看程式碼。
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define max(a,b) (a>b ? a:b)
#define min(a,b) (a<b ? a:b)
#define swap(a,b) (a^=b^=a^=b)
#define maxn 105000
#define minn -105
#define ll long long int
#define ull unsigned long long int
#define uint unsigned int
inline int read()
{
int ans=0;
char last=' ',ch=getchar();
while(ch<'0'|ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans;
return ans;
}
int n;
vector<int>node[maxn];
int dfs(int cur,int fa)
{
int lft=0;
for(int k:node[cur])
{
if(fa==k)continue;
lft+=dfs(k,cur);
}
if(lft)lft--;
else lft++;
return lft;
}
int main()
{
cin>>n;
for(int i=0;i<n-1;i++)
{
int a,b;
cin>>a>>b;
node[a].push_back(b);
node[b].push_back(a);
}
if(dfs(1,1))cout<<"Alice\n";
else cout<<"Bob\n";
return 0;
}
E.雙指標
有n個人去旅遊,可以選擇開車或者騎摩的,一輛車帶m個人(含司機),要求司機年齡大於lc,租金pc,
一輛摩的只能帶一個人(司機自己),要求司機年齡大於lm,租金pm
每次花t塊錢,把一個人的年齡續給另一個人。
要求1.年齡不能小於1,2.對每個人最多用d次(包括給的人和接的人)
現在告訴你所有人的年齡ai,問你最少花費把所有人都帶走,如果做不到輸出-1
思路:
通過雙指標來模擬開車和騎車人的數量,其中一項確定後另一項就也確定了
在模擬的同時記錄最小值
如果不能滿足情況就輸出-1
#include<bits/stdc++.h>
using namespace std;
long long n,k,lm,lc,pm,pc,t,d;
long long nd,hv,co;
long long a[100010];
int main()
{
cin>>n>>k>>lc>>pc>>lm>>pm>>t>>d;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
long long ans=0x3f3f3f3f3f3f3f3f;
int cnt=0;
for(int i=1;i<=n;i++)
{
hv+=max(0ll,min(1ll*d,a[i]-lm));
nd+=max(0ll,min(1ll*d,lm-a[i]));
if(a[i]+d<lm)
cnt++;
}
co=n*pm+nd*t;
if(!cnt&&hv>=nd)
ans=co;
int l=1,r=n;
int tot=0;
// cout<<"1:"<<nd<<" "<<hv<<endl;
while(r-l+1>=k)
{
// cout<<"1:"<<nd<<" "<<hv<<endl;
tot++;
for(int i=0;i<k-1;i++)
{
nd-=max(0ll,min(1ll*d,lm-a[i+l]));
hv+=min(1ll*d,min(1ll*lm,a[i+l])-1);
if(a[i+l]+d<lm)
cnt--;
}
nd+=max(0ll,lc-max(1ll*lm,a[r]));
hv-=max(0ll,min(1ll*d,a[r]-lm));
hv+=max(0ll,min(1ll*d,a[r]-lc));
if(a[r]+d<lc)
break;
co=pm*(n-tot*k)+tot*pc+nd*t;
if(!cnt&&hv>=nd)
ans=min(co,ans);
l+=k-1;
r--;
// cout<<"2:"<<nd<<" "<<hv<<endl;
}
if(a[r]+d>=lc)
{
// cout<<"1:"<<nd<<" "<<hv<<endl;
tot++;
for(int i=0;i<r-l;i++)
{
nd-=max(0ll,min(1ll*d,lm-a[i+l]));
hv+=min(1ll*d,min(1ll*lm,a[i+l])-1);
if(a[i+l]+d<lm)
cnt--;
}
nd+=max(0ll,lc-max(1ll*lm,a[r]));
hv-=max(0ll,min(1ll*d,a[r]-lm));
hv+=max(0ll,min(1ll*d,a[r]-lc));
co=tot*pc+nd*t;
if(!cnt&&hv>=nd)
ans=min(co,ans);
// cout<<"2:"<<nd<<" "<<hv<<endl;
}
if(ans==0x3f3f3f3f3f3f3f3f)
cout<<-1;
else
cout<<ans;
return 0;
}
A.線段樹維護單點修改,區間合併
有n個數字排列成一個環,每次可選擇一個數字和它相鄰的兩個數字,將這個數字變為三個中最大的或最小的。
問對所有的i∈[1,m]。最小的將所有的數字變為i的運算元,若不存在這樣的方案數,輸出-1.
1<=n,m<=2e5
思路:
對於一個選定的i,我們計所有大於i的數為1,小於i的數為-1,等於i的記為0,則
[0,1,1]直接取最小
[0,1,-1]先取最小,再取最大
[0,-1,-1]直接取最大
[0,-1,1]先取最大,再取最小
可以看到,-1與1的連續導致運算元增加
-1 1時增加一次
-1 1 -1時可先改中間,總運算元增加一次
-1 1 -1 1時增加兩次
-1 1 -1 1 -1時可改2 4位置的,總運算元增加兩次
由此可見一個長度為m的-1 1串會增加總運算元m/2次
開二倍陣列使之成為一個環
維護從一個0開始長度為n的區間中-1 1串的貢獻加上所有非零數的個數即可
對每個區間記錄從左開始的-1 1長度與從右開始的
合併兩個區間時若從左開始的長度等於區間長度,則將右邊的左長度也加入合成區間的左長度,否則只繼承左邊的左長度,右邊也做相同的處理
計算貢獻時若左邊的右端點與右邊的左端點異號,則需更新在此處的貢獻為(左邊的右長度+右邊的左長度)/2
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct tnode{
int to,next;
}e[800080];
struct tree{
int l,r,ans,l1,r1;
}t[1600080];//開始時習慣性的看題目2e5的範圍開了8e5的陣列,忘了自己為了讓陣列成環已經擴大到了4e5的範圍,線段樹應該開到4e5的四倍
int head[800080],tot,a[800080],hash1[800020];
void add(int l,int r)
{
e[++tot].to=r;
e[tot].next=head[l];
head[l]=tot;
}
void build(int i,int l,int r)
{
if(l==r)
{
t[i].l=1;
t[i].r=1;
t[i].l1=l;
t[i].r1=r;
t[i].ans=0;
return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
t[i].l=1;
t[i].r=1;
t[i].l1=l;
t[i].r1=r;
t[i].ans=0;
}
int now;
void update(int i,int zhi)
{
if(t[i].l1>zhi||t[i].r1<zhi)return;
if(t[i].l1==t[i].r1)
{
if(a[t[i].l1]==now)
{
t[i].l=0;
t[i].r=0;
t[i].ans=0;
}
else
{
t[i].l=1;
t[i].r=1;
t[i].ans=0;
}
return;
}
update(i<<1,zhi);
update(i<<1|1,zhi);
if(t[i<<1].l==t[i<<1].r1-t[i<<1].l1+1&&(a[t[i<<1].r1]-now)*(a[t[i<<1|1].l1]-now)<0)
{
t[i].l=t[i<<1].l+t[i<<1|1].l;
}
else t[i].l=t[i<<1].l;
if(t[i<<1|1].r==t[i<<1|1].r1-t[i<<1|1].l1+1&&(a[t[i<<1|1].l1]-now)*(a[t[i<<1].r1]-now)<0)
{
t[i].r=t[i<<1].r+t[i<<1|1].r;
}
else t[i].r=t[i<<1|1].r;
t[i].ans=t[i<<1].ans+t[i<<1|1].ans;
if((a[t[i<<1].r1]-now)*(a[t[i<<1|1].l1]-now)<0)//開始時想偷懶直接用乘積來判斷兩個區間的端點異號,沒注意到會爆int
t[i].ans+=(t[i<<1].r+t[i<<1|1].l)/2-t[i<<1].r/2-t[i<<1|1].l/2;
}
int sum(int i,int l,int r)
{
if(t[i].l1>r||t[i].r1<l)return 0;
if(t[i].l1>=l&&t[i].r1<=r)return t[i].ans;
int mid=(t[i].l1+t[i].r1)>>1;
int res=0;
res+=sum(i<<1,l,r);
res+=sum(i<<1|1,l,r);
int l1,r1;
if(l<=mid&&mid+1<=r)
{
l1=min(t[i<<1].r,mid-max(l,t[i].l1)+1);
r1=min(t[i<<1|1].l,min(r,t[i].r1)-mid);
if((a[t[i<<1].r1]-now)*(a[t[i<<1|1].l1]-now)<0)
{
res+=(l1+r1)/2-l1/2-r1/2;
// cout<<"error"<<min(r,t[i].r)-mid<<" "<<r1<<"\n";
}
}
return res;
}
void print(int n)
{
for(int i=1;i<=2*n;i++)
cout<<t[i].l1<<" "<<t[i].r1<<" "<<t[i].ans<<" "<<t[i].l<<" "<<t[i].r<<"\n";
}
signed main()
{
// freopen("std.in","r",stdin);
// freopen("std.out","w",stdout);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i+n]=a[i];
add(a[i],i);
hash1[a[i]]++;
}
build(1,1,n<<1);
for(int i=1;i<=m;i++)
{
now=i;
for(int j=head[i-1];j;j=e[j].next)
{
update(1,e[j].to);
update(1,e[j].to+n);
}
if(hash1[i]==0)
{
printf("-1 ");
}
else
{
for(int j=head[i];j;j=e[j].next)
{
update(1,e[j].to);
update(1,e[j].to+n);
}
// printf("%d %d\n",i,sum(1,e[head[i]].to,e[head[i]].to+n-1));
printf("%d ",n-hash1[i]+sum(1,e[head[i]].to,e[head[i]].to+n-1));
}
// cout<<i<<"\n";
// print(n<<2);
// cout<<"\n";
// cout<<i<<" "<<e[head[i]].to<<" "<<e[head[i]].to+n-1<<"\n";
// cout<<sum(1,1,5)<<"\n";
}
return 0;
}