1. 程式人生 > 實用技巧 >AcWing 105 七夕祭 (貪心)

AcWing 105 七夕祭 (貪心)

題目連結:https://www.acwing.com/problem/content/107/

我們發現:
交換左右相鄰的攤點,只會改變某兩列的數量,同理,交換上下相鄰的攤點有改變某兩行的數量
於是我們就可以將行和列分開考慮

而對於行和列:就是經典的均分紙牌問題,不過這裡是環形均分紙牌
必然有一種最優解:有相鄰兩個位置不發生交換

假設有\(T\)張牌,\(N\)個人,
\(A[i] = \frac{T}{N}\),\(S[i]\)為字首和
從第\(k\)個人把環斷開,\(S[i]\) 的變化是都減掉 \(S[k]\)
所以答案即為:$$\sum_{i=1}^{N}\mid S[i] - S[k] \mid$$
這就是貨倉選址問題,把\(S\)

排序,取中位數即為\(S[k]\)的最優解

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;
typedef long long ll;

const int maxn = 100010;

int n, m, T; 
int x[maxn] ,y[maxn], sum[maxn];

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; }

int main(){
	n = read(), m = read(), T = read();
	
	int X, Y;
	for(int i=1;i<=T;++i){
		X = read(), Y = read();
		++x[X], ++y[Y];
	}

	int flagx = 0, flagy = 0;ll ansx = 0, ansy = 0;
	
	// 行 
	if(T % n != 0) flagx = 1;
	if(!flagx){
		int num = T / n;
		for(int i=1;i<=n;++i) x[i] -= num;
		for(int i=1;i<=n;++i) sum[i] = sum[i-1] + x[i];
		
		sort(sum + 1, sum + 1 + n);
		
		int mid = sum[(n+1)/2];
		for(int i=1;i<=n;++i) ansx += abs(sum[i] - mid);
	} 
	
	memset(sum, 0, sizeof(sum));
	// 列
	if(T % m != 0) flagy = 1;
	if(!flagy){
		int num = T / m;
		for(int i=1;i<=m;++i) y[i] -= num;
		for(int i=1;i<=m;++i) sum[i] = sum[i-1] + y[i];

		sort(sum + 1, sum + 1 + m);
		
		int mid = sum[(m+1)/2];
		for(int i=1;i<=m;++i) ansy += abs(sum[i] - mid);
	}  
	
	if(flagx && flagy) printf("impossible\n");
	else if(!flagx && flagy){
		printf("row %lld\n",ansx);
	}else if(flagx && !flagy){
		printf("column %lld\n",ansy);
	}else{
		printf("both %lld\n",ansx + ansy);
	}

	return 0;
}