洛谷 P10512 序列合併

wljss發表於2024-05-20

哭死,比賽的時候完全想歪了,想的是考慮一次合併能造成多大的貢獻,按照貢獻排序然後合併。這樣做只能考慮區域性造成的貢獻,然而最後算的時候要考慮整體,所以並不是很對。

正著想沒有思路就可以倒著想,考慮列舉答案。

合併k次,意味著最後是n-k個數。

經典從二進位制高位到低位考慮,考慮這一位(假設為第i位)能否出現在答案裡?那我們就讓原序列最後合併完後的數每個數第i位都是1,在此要求下讓合併完的數儘可能的多(只要最後留下的數的個數大於等於n-k就可以,然後我們就能接著考慮二進位制下一位)。怎們考慮讓合併完的數儘可能的多?我們設nt[now][j]表示第now個數及以後最早出現“二進位制第j位為1”的位置在哪,就很好讓最後合併完的數儘可能多了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,ans;
const int N=200010;
int a[N],nt[N][32],las[233];
int pan(int c)
{
	int cnt=0,now=1;
	while(now<=n)
	{
		int maxx=0;
		for(int j=1;j<=31;++j)
			if((c>>(j-1))&1)
			{
				maxx=max(maxx,nt[now][j]);
			}
		//cout<<" -> "<<now<<" "<<maxx<<endl;
		if(maxx<=n)++cnt,now=maxx+1;
		else break;
	}
	return cnt>=k;
}
signed main()
{
	cin>>n>>k;k=n-k;
	for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
	for(int i=1;i<=31;++i)las[i]=n+1;
	for(int i=n;i>=1;--i)
	{
		for(int j=1;j<=31;++j)
			if((a[i]>>(j-1))&1)las[j]=i;
		for(int j=1;j<=31;++j)nt[i][j]=las[j];//,cout<<"-- "<<i<<" "<<j<<" "<<nt[i][j]<<endl;
	}
	for(int i=31;i>=1;--i)
	{
		ans|=(1ll<<(i-1));
		if(pan(ans)==0)ans^=(1ll<<(i-1));
	}
	cout<<ans;
	return 0;
} 
/*
5 2
2 1 2 3 1


20 7
46734244 7155744 419668125 71371568 686112 90931877 235739656 174560001 73941537 157614741 557848156 172496544 449681808 258216481 628704657 317530505 436971280 329908401 168398248 163412001

4194304
*/