2024.11.20 NOIP模擬 - 模擬賽記錄

Jerrycyx發表於2024-11-20

異或(xor

每次所加三角形的範圍如圖所示:

image

這道題做法較多,我是透過兩組差分與字首和來做的。

首先需要一個三角形差分,使每一次在差分陣列中修改時,影響到的範圍是一個三角形,比如這樣(紅色點為 \((x,y)\),即 \((r,c)\)):

image

假設我們真正需要修改的三角形是橙色部分:

image

那麼聯絡到正常差分,很容易想到在 \((x+l,y+l)\) 的位置減去多出的值:

image

然而,左下方還多出了一塊矩形,而這不是我們想要的,所以我們可以再額外維護一個矩形的二維差分來抵消這部分多出的貢獻(像正常二維差分一樣在這塊矩形區域內全部減去多出的貢獻即可):

image

最後,對所有差分陣列求字首和,矩形差分陣列的字首和很好求,而三角形差分陣列的字首和則要額外注意。根據差分陣列影響的三角形範圍倒推可以得知,深藍色部分的三角字首和應該是這一部分的和:

image

其中深藍色塊的三角字首和等於紫色塊的三角字首和加上深灰色部分,即(\(sum\) 表示三角字首和,\(dif\) 表示差分陣列):

\[sum_{i,j}=sum_{i-1,j-1}+\sum_{k=1}^{i-1}dif_{k,j} \]

然後就做出來了,時間複雜度 \(O(N^2)\)

#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;

const int N=1005;
int n,q;
LL tr_dif[N][N],sq_dif[N][N];
LL tr_sum[N][N],sq_sum[N][N];
//triangle, square 

inline void Add(int x,int y,int l,int s)
{
	tr_dif[x][y]+=s;
	if(x+l<=n&&y+l<=n) tr_dif[x+l][y+l]-=s;
	if(x+l<=n) sq_dif[x+l][y]-=s;
	if(x+l<=n&&y+l<=n) sq_dif[x+l][y+l]+=s;
	return;
}

void Calc_sum()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			sq_sum[i][j]=sq_dif[i][j]+sq_sum[i-1][j]+sq_sum[i][j-1]-sq_sum[i-1][j-1];
	for(int j=1;j<=n;j++)
	{
		LL tmp=0;
		for(int i=1;i<=n;i++)
		{
			tmp+=tr_dif[i][j];
			tr_sum[i][j]=tr_sum[i-1][j-1]+tmp;
		}
	}
	return;
}

int main()
{
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	
	scanf("%d%d",&n,&q);
	for(int i=1;i<=q;i++)
	{
		int x,y,l,s;
		scanf("%d%d%d%d",&x,&y,&l,&s);
		Add(x,y,l,s);
	}
	Calc_sum();
	LL ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			ans^=tr_sum[i][j]+sq_sum[i][j];
	printf("%lld\n",ans);
	return 0;
}

對於每一行維護一個差分的做法時間複雜度是 \(O(QN)\) 的,似乎也可以卡一卡。

遊戲(game

好啊,詐騙題是吧,Subtask 用上癮了是吧!?

先說暴力,暴力就是列舉所有情況。具體來說,所以對於每一個 \(b_i\),嘗試進行操作一和操作二,得到兩個結果,再根據當前角色是 Alice 還是 Bob 選擇取這兩個結果的最大或最小值返回(因為每一個人的策略是確定的,所以選擇了操作以後,後面所得到的答案是確定的)。

時間複雜度 \(O(2^M \times N)\),期望得分 \(15\) 分。

點選檢視程式碼
const int M_s14=25;
bitset<M_s14> div[N],chs;
LL DFS(int p,bool r) //0A,1B
{
	if(p>m)
	{
		LL res=0;
		for(int i=1;i<=n;i++)
		{
			bool flag=true;
			for(int j=1;j<=m;j++)
				if((chs[j] && a[i]%b[j]) || (!chs[j] && a[i]%b[j]==0))
				{
					flag=false;
					break;
				}
			if(flag) res+=a[i];
		}
		return res;
	}
	chs[p]=true; LL res1=DFS(p+1,!r);
	chs[p]=false; LL res2=DFS(p+1,!r);
	if(r) return max(res1,res2);
	else return min(res1,res2);
}
void Solve()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i]%b[j]==0) div[i][j]=true;
	printf("%lld\n",DFS(1,0));
	return;
}

觀察這一語句 if((chs[j] && a[i]%b[j]) || (!chs[j] && a[i]%b[j]==0)) 可以發現,對於某一個數,能夠保留下它所需要的操作是確定的(因為兩種操作互斥,所以能消掉它的操作都必須不選,不能消掉它的都必須選),所以我們可以預處理出保留某一個數所需要的操作序列,用類似狀態壓縮的方式儲存下來,這樣就不用每次都遍歷所有 \(n\) 個元素了。

時間複雜度 \(O(2^M)\),期望得分 \(30\) 分。

點選檢視程式碼
const int M_s14=20;
LL sum[1<<(M_s14+1)];
LL DFS(int p,unsigned int chs) //0A,1B
{
	if(p>=m) return sum[chs];
	LL res1=DFS(p+1,chs);
	LL res2=DFS(p+1,chs|1<<p);
	if(p&1) return max(res1,res2);
	else return min(res1,res2);
}
void Solve()
{
	for(int i=1;i<=n;i++)
	{
		unsigned int div=0;
		for(int j=1;j<=m;j++)
			if(a[i]%b[j]==0) div|=1<<(j-1);
		sum[div]+=a[i];
	}
	printf("%lld\n",DFS(0,0));
	return;
}

對於特殊性質“所有的 \(b_i\) 都相等”,這是我賽事的草稿,講得還算清楚:

最後一步:
A: \(\min(0,sum)\)
B: \(\max(0,sum)\)

倒數第二步:
A: 和為正數-直接幹成 \(0\)(否則 B 在最後一步會搞出正數)
和為負數-保留,但B最後還是會弄成 \(0\)
B: 和為負數-直接幹成 \(0\)(否則 A 在最後一步會搞出負數)
和為正數-保留,但A最後還是會弄成 \(0\)

綜上所述:在倒數第二步,和為正數、零、負數最後都會被幹成 \(0\)
故步數 \(\ge 2\) 時答案必為 \(0\)

特判:步數 \(=1\) 時:A 先手,直接取 \(\min\)(\(b\) 倍數和、非 \(b\) 倍數和)

點選檢視程式碼
void Solve()
{
	if(m==1)
	{
		LL b_sum=0,nb_sum=0;
		for(int i=1;i<=n;i++)
		{
			if(a[i]%b[1]) nb_sum+=a[i];
			else b_sum+=a[i];
		}
		printf("%lld\n",min(b_sum,nb_sum));
	}
	else printf("0\n");
	return;
}

上面的做法是沒有前途的。