【BZOJ 3032】 七夕祭
【題目鏈接】
https://www.lydsy.com/JudgeOnline/problem.php?id=3032
【算法】
交換左右兩個相鄰格子的攤點,不會改變這一行的攤點個數
交換上下兩個相鄰格子的攤點,不會改變這一列的攤點個數
因此,題目中所要求的兩個問題是獨立的,可以分別計算,以第一問 : 每列攤點個數相等,進行討論 :
我們將每列中的初始攤點個數記為Ai,如果感興趣的攤點總數不能被M整除,則無解,否則,等價於一個“環形均分紙牌”的問題,
如果不允許“環形傳遞”,那麽最少移動步數為 sigma( | Si | ),Si為Ai - T / M的前綴和
如果允許,仔細思考後會發現,一定有一種最優解的方案,使得環上兩個人不進行傳遞,考慮將環斷開 :
假設斷開位置為k,那麽斷開後,每個人的紙牌個數和前綴和分別為 :
Ak+1 Sk+1 - Sk
Ak+2 Sk+2 - Sk
...
Am Sm - Sk
A1 S1 + Sm - Sk
...
Ak Sk + Sm - Sk
因為Sm = 0,所以,前綴和數組與一般情況的差別就是每個位置都減了Sk
所以若斷開位置為k,最少交換次數為 : sigma( | Si - Sk| ) , 當Sk取前綴和數組S的中位數時,這個式子的值最小,也就是說,我們將S數組從小到大排序,取中位數Sk就是最優解
那麽,這道題就迎刃而解了!
【代碼】
#include<bits/stdc++.h> using namespace std; #define MAXN 100010 struct pos { int x,y; } a[MAXN]; int i,N,M,T; long long ans; longlong s[MAXN]; inline void solve1() { int i; memset(s,0,sizeof(s)); for (i = 1; i <= T; i++) s[a[i].y]++; for (i = 1; i <= M; i++) s[i] -= T / M; for (i = 2; i <= M; i++) s[i] += s[i-1]; sort(s+1,s+M+1); for (i = 1; i <= M; i++) ans += abs(s[i] - s[(M+1)>>1]); } inline void solve2() { int i; memset(s,0,sizeof(s)); for (i = 1; i <= T; i++) s[a[i].x]++; for (i = 1; i <= N; i++) s[i] -= T / N; for (i = 2; i <= N; i++) s[i] += s[i-1]; sort(s+1,s+N+1); for (i = 1; i <= N; i++) ans += abs(s[i] - s[(N+1)>>1]); } int main() { scanf("%d%d%d",&N,&M,&T); for (i = 1; i <= T; i++) scanf("%d%d",&a[i].x,&a[i].y); if (T % N != 0 && T % M != 0) { printf("impossible"); return 0; } if (T % M == 0 && T % N != 0) { printf("column "); solve1(); } else if (T % M != 0 && T % N == 0) { printf("row "); solve2(); } else { printf("both "); solve1(); solve2(); } printf("%lld\n",ans); return 0; }
【BZOJ 3032】 七夕祭