1. 程式人生 > 其它 >CF1495E Qingshan and Daniel

CF1495E Qingshan and Daniel

一、題目

點此看題

\(n\) 個人排成環,第 \(i\) 個人有 \(a_i\) 張卡牌,屬於陣營 \(t_i\),首先第一個人會打出卡牌,然後和當前打出卡牌的人屬於不同陣營且距離它最近(向右的距離)的人會接著打出卡牌。

問每個人最後的卡牌數,輸入和輸出都需要按題目所述進行加密。

\(n\leq 5\cdot 10^6\)

二、解法

首先做一些基本的觀察:所屬陣營卡牌總數少的陣營都是 \(0\)

然後考慮打牌的形式是從序列上選取一個環,然後環上每個人依次打牌之後有人無牌退出遊戲。這相當於每次刪去環上的最小值,我們考慮維護這個環:

  • 考慮刪除的位置是 \(id\),如果下一個位置同色則在環上替換成下一個位置。
  • 如果下一個位置異色,那麼它在環上的前驅需要把它在環上的後繼的同色段合併起來。

感覺暴力維護的話需要 \(\tt set/priority\) 之類的資料結構,\(O(n\log n)\) 無法通過。

但是沒有必要按照時間順序來維護環,我們考慮一個很小的區域性問題,對於相鄰兩個屬於不同陣營的人,當它們出現在環上時,可以看成它們"決鬥"之後其中一個掛掉。

我們欽定起點屬於總數少的陣營(否則我們暴力先跳一步),然後從它開始線性掃,如果遇到相同陣營的就讓 \(cnt\leftarrow cnt+a_i\),如果遇到不同陣營的就讓它和 \(cnt\) "決鬥",顯然這樣做是把我上面說的巧妙地模擬了出來,時間複雜度 \(O(n)\)

三、總結

不只是此題,很多題目簡化實現\(/\)優化複雜度的關鍵都是減少我們維護的量,我們可以遵守一個 整體->區域性 的思維過程,先考慮整體的變化再反映到區域性上便於快速計算。

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int N = 200005;
const int M = 5000005;
const int MOD = 1e9+7;
#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,cnt,ans,seed,base,sum[2],a[M],tmp[M],t[M];
int random()
{
	int ret=seed;
	seed=(seed*base+233)%MOD;
	return ret;
}
void decode()
{
	n=read();m=read();
	for(int i=1,ls=0;i<=m;i++)
	{
		int p=read(),k=read(),b=read(),w=read();
		seed=b;base=w;
		for(int j=ls+1;j<=p;j++)
		{
			t[j]=random()%2;
			a[j]=random()%k+1;
		}
		ls=p;
	}
}
signed main()
{
	decode();
	for(int i=1;i<=n;i++)
		sum[t[i]]+=a[i],tmp[i]=a[i];
	int st=1,nw=1;
	if(sum[t[1]]>sum[!t[1]])//switch the start pos
	{
		sum[t[1]]--;a[1]--;
		while(st<=n && t[st]==t[1]) st++;
	}
	nw=st;
	if(st<=n) while(sum[t[st]]>0)
	{
		if(t[nw]==t[st])
			cnt+=a[nw],a[nw]=0;
		else
		{
			int o=min(cnt,a[nw]);
			a[nw]-=o;cnt-=o;sum[t[st]]-=o;
		}
		nw=nw%n+1;
	}
	ans=1;
	for(int i=1;i<=n;i++)
		ans=ans*((((tmp[i]-a[i])^(i*i))+1)%MOD)%MOD;
	printf("%lld\n",ans);
}