CH - 0502 七夕祭(思维+中位数优化+前缀和优化)
題目鏈接:點(diǎn)擊查看
題目大意:七夕節(jié)因牛郎織女的傳說(shuō)而被扣上了「情人節(jié)」的帽子。于是TYVJ今年舉辦了一次線下七夕祭。Vani同學(xué)今年成功邀請(qǐng)到了cl同學(xué)陪他來(lái)共度七夕,于是他們決定去TYVJ七夕祭游玩。
TYVJ七夕祭和11區(qū)的夏祭的形式很像。矩形的祭典會(huì)場(chǎng)由N排M列共計(jì)N×M個(gè)攤點(diǎn)組成。雖然攤點(diǎn)種類繁多,不過(guò)cl只對(duì)其中的一部分?jǐn)傸c(diǎn)感興趣,比如章魚(yú)燒、蘋(píng)果糖、棉花糖、射的屋……什么的。Vani預(yù)先聯(lián)系了七夕祭的負(fù)責(zé)人zhq,希望能夠通過(guò)恰當(dāng)?shù)夭贾脮?huì)場(chǎng),使得各行中cl感興趣的攤點(diǎn)數(shù)一樣多,并且各列中cl感興趣的攤點(diǎn)數(shù)也一樣多。
不過(guò)zhq告訴Vani,攤點(diǎn)已經(jīng)隨意布置完畢了,如果想滿足cl的要求,唯一的調(diào)整方式就是交換兩個(gè)相鄰的攤點(diǎn)。兩個(gè)攤點(diǎn)相鄰,當(dāng)且僅當(dāng)他們處在同一行或者同一列的相鄰位置上。由于zhq率領(lǐng)的TYVJ開(kāi)發(fā)小組成功地扭曲了空間,每一行或每一列的第一個(gè)位置和最后一個(gè)位置也算作相鄰。現(xiàn)在Vani想知道他的兩個(gè)要求最多能滿足多少個(gè)。在此前提下,至少需要交換多少次攤點(diǎn)。
題目分析:中文題目,題意不難理解,首先我們應(yīng)該知道,行和列是兩個(gè)獨(dú)立的個(gè)體,在交換相鄰兩行的時(shí)候,對(duì)列是沒(méi)有貢獻(xiàn)的,同理,在交換相鄰兩列的時(shí)候,對(duì)行也是沒(méi)有貢獻(xiàn)的,所以我們只針對(duì)于交換相鄰兩列的情況討論:
首先列數(shù)一共有m列,攤數(shù)一共有t個(gè),若t%m不為0,那么對(duì)于列是無(wú)解的,如果有解的話,那么每一列最后都需要有t/m個(gè)攤點(diǎn)才行,而相鄰兩個(gè)可以交換,我們先假設(shè)當(dāng)前的模型不是環(huán)狀的而是線狀的,a[1]是開(kāi)頭,a[m]是結(jié)尾,這樣一來(lái)就能線性遞推出答案了,比如:
用上面的關(guān)系就能線性遞推出答案了
但實(shí)際上題目并不是這么簡(jiǎn)單的,題目將線性的模型改成了環(huán)狀的模型,也就是說(shuō)必定存在著一種最優(yōu)解,能讓答案最小,換句話說(shuō),必定有兩個(gè)相鄰的列之間沒(méi)有發(fā)生攤位的交換,就像上面提到的線性模型一樣,開(kāi)頭節(jié)點(diǎn)和末尾節(jié)點(diǎn)之間就沒(méi)有發(fā)生攤位的交換,在這種情況下求最優(yōu)解,我們最樸素的方法是枚舉每一個(gè)斷點(diǎn),一共需要枚舉n次,每次都需要進(jìn)行O(n)的遞推,才能計(jì)算出答案,從而維護(hù)出最小值,總的時(shí)間復(fù)雜度為n*n+m*m,這個(gè)題目給出的n和m都達(dá)到了1e5的級(jí)別,所以n*n肯定會(huì)超時(shí)的,我們需要想辦法優(yōu)化
我們可以一開(kāi)始就讓a[i]數(shù)組的每一項(xiàng)都減去t/m,最后結(jié)果為正說(shuō)明需要往后面扔,為負(fù)說(shuō)明需要從后面拿,這樣我們維護(hù)一個(gè)前綴和sum[i],第i項(xiàng)對(duì)答案的貢獻(xiàn)就是abs(sum[i])了,其實(shí)思想和上面的方法類似,只不過(guò)上面的思想相當(dāng)于模擬整個(gè)過(guò)程,而這里的思想是將模型抽象出來(lái),轉(zhuǎn)換為數(shù)學(xué)公式而已了,比如第i個(gè)位置的貢獻(xiàn)是a[i],先不管他的正負(fù),我們都需要將a[i]先轉(zhuǎn)移(拿走或提供)給a[i+1],然后再計(jì)算a[i+1]的貢獻(xiàn),隨后再將a[i+1]的貢獻(xiàn)轉(zhuǎn)移給a[i+2],如此往復(fù)就是一個(gè)前綴和了,并且當(dāng)前前綴和的最后一項(xiàng)一定等于0,即sum[m]=0
但上述轉(zhuǎn)換只是換了種方法求每個(gè)位置對(duì)答案的貢獻(xiàn),實(shí)質(zhì)上時(shí)間復(fù)雜度還是O(n),并沒(méi)有起到優(yōu)化的作用,所以我們需要繼續(xù)轉(zhuǎn)換,到這里我們發(fā)現(xiàn)對(duì)于求答案,也只能優(yōu)化為O(n)了,實(shí)在是沒(méi)有辦法再優(yōu)化了,那么我們就只能著手于優(yōu)化另一個(gè)O(n),也就是枚舉n個(gè)端點(diǎn)選出最小值的過(guò)程了
因?yàn)橛辛饲熬Y和sum[i]的輔助,我們可以設(shè)置某個(gè)斷點(diǎn)k,此時(shí)第k+1個(gè)點(diǎn)充當(dāng)?shù)谝粋€(gè)點(diǎn),第k個(gè)點(diǎn)充當(dāng)最后一個(gè)點(diǎn),這樣我們將剛才求得的前綴和稍加轉(zhuǎn)換:
第幾項(xiàng)? ?前綴和
a[k+1]? ? sum[k+1]-sum[k]
a[k+2]? ? sum[k+2]-sum[k]
....
a[m]? ? ? ?sum[m]-sum[k]
a[1]? ? ? ? sum[1]+sum[m]-sum[k]
....
a[k]? ? ? ? sum[k]+sum[m]-sum[k]
這樣我們?cè)谥罃帱c(diǎn)的下標(biāo)后,就可以O(shè)(1)求出所需要的前綴和了,也就不需要每次再用O(m)重新維護(hù)前綴和了
因?yàn)槲覀冊(cè)谏厦嫣岬竭^(guò),sum[m]=0,那么上面的每一項(xiàng)都可以總結(jié)為一個(gè)通項(xiàng)了,對(duì)于第i項(xiàng),他的前綴和就是sum[i]-sum[k],那么當(dāng)前位置對(duì)答案的貢獻(xiàn)也就是abs(sum[i]-sum[k]),我們現(xiàn)在只要找到k,使得i=[1,m]的貢獻(xiàn)和最小就可以了,上面我們最樸素的方法是暴力枚舉,線性維護(hù)最小值,但被我們直接駁回了,我們通過(guò)觀察以及總結(jié)可以得出結(jié)論,若想讓上面的式子貢獻(xiàn)和最小,讓sum[k]選取sum數(shù)組的中位數(shù)即可,這樣我們只需要對(duì)sum數(shù)組排序,時(shí)間復(fù)雜度為nlogn,忽略其余常數(shù)的話,這個(gè)題目的額時(shí)間復(fù)雜度就優(yōu)化為nlogn+mlogm了,完美解決
不得不提一句,這個(gè)題目和“貨倉(cāng)選址”那個(gè)題目又有點(diǎn)不同,貨倉(cāng)選址那個(gè)題目也是讓在數(shù)軸上選取一個(gè)位置,使得abs(a[i]-x)的貢獻(xiàn)和最小,可以得出的結(jié)論是:
所以在做這個(gè)題目的時(shí)候,我直接按照分類討論,當(dāng)n為奇數(shù)時(shí),求得的中位數(shù)是(a[n/2]~a[n/2+1])/2,但只得了90分,最后一拍腦瓜才知道,貨倉(cāng)選址這個(gè)題目的中位數(shù)是可以在數(shù)軸上任意選取的,但這個(gè)題目的中位數(shù)是只能再sum數(shù)組中選取,所以如果除以2的話,求出來(lái)的答案可能不是sum數(shù)組中的元素,所以求出來(lái)的答案就不滿足題意了
那么對(duì)于這個(gè)題目,我們也無(wú)需分奇偶討論了,統(tǒng)統(tǒng)以sum[(n+1)/2]作為中位數(shù)就可以了
還有一個(gè)細(xì)節(jié)需要注意,就是前綴和和最終答案可能會(huì)爆int,記得開(kāi)longlong就好了
代碼:
#include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=1e5+100;int n,m,t;LL ans_r,ans_c;struct Pos {int x,y; }a[N];LL c[N];bool solve_r()//處理行 {if(t%n)return false;memset(c,0,sizeof(c));for(int i=1;i<=t;i++)//記錄每一列有多少個(gè)攤位c[a[i].x]++;for(int i=1;i<=n;i++)//維護(hù)前綴和c[i]=c[i]-t/n+c[i-1];sort(c+1,c+1+n);//排序LL temp=c[(n+1)/2];//中位數(shù)ans_r=0;for(int i=1;i<=n;i++)//計(jì)算貢獻(xiàn)和ans_r+=llabs(c[i]-temp);return true; }bool solve_c()//處理列,同上 {if(t%m)return false;memset(c,0,sizeof(c));for(int i=1;i<=t;i++)c[a[i].y]++;for(int i=1;i<=m;i++)c[i]=c[i]-t/m+c[i-1];sort(c+1,c+1+m);LL temp=c[(m+1)/2];ans_c=0;for(int i=1;i<=m;i++)ans_c+=llabs(c[i]-temp);return true; }int main() { // freopen("input.txt","r",stdin); // ios::sync_with_stdio(false);scanf("%d%d%d",&n,&m,&t);for(int i=1;i<=t;i++)scanf("%d%d",&a[i].x,&a[i].y);bool flag_c=solve_c();bool flag_r=solve_r();if(flag_c&&flag_r)printf("both %lld\n",ans_c+ans_r);else if(flag_c)printf("column %lld\n",ans_c);else if(flag_r)printf("row %lld\n",ans_r);elseprintf("impossible\n");return 0; }?
總結(jié)
以上是生活随笔為你收集整理的CH - 0502 七夕祭(思维+中位数优化+前缀和优化)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: CodeForces - 670C Ci
- 下一篇: CH - 0501 货仓选址(中位数)