Kpmcup#0 模擬賽 總結

歲月難得沉默秋風厭倦漂泊
夕陽賴着不走掛在牆頭捨不得我
昔日伊人耳邊話已和潮聲向東流
再回首往事也隨楓葉一片片落
愛已走到盡頭恨也放棄承諾
命運自認幽默想法太多由不得我
壯志凌雲幾分愁知己難逢幾人留
再回首卻聞笑傳醉夢中
笑嘆詞窮古癡今狂終成空
刀鈍刃乏恩斷義絕夢方破
路荒已嘆飽覽足跡沒人懂
多年望眼欲穿過紅塵滾滾我沒看透
自嘲墨盡千情萬怨英傑愁
曲終人散發花鬢白紅顏歿
燭殘未覺與日爭輝圖消瘦
當淚乾血隱狂湧白雪紛飛都成紅
——《逍遙嘆》

A. BZOJ 3436: 小K的農場 差分約束系統

title

BZOJ 3436
LUOGU 1993
Description

背景
小K是個特麼喜歡玩MC的孩紙。。。
描述
小K在MC裏面建立很多很多的農場,總共n個,以至於他自己都忘記了每個農場中種植作物的具體數量了,他只記得一些含糊的信息(共m個),以下列三種形式描述:農場a比農場b至少多種植了c個單位的作物,農場a比農場b至多多種植了c個單位的作物,農場a與農場b種植的作物數一樣多。但是,由於小K的記憶有些偏差,所以他想要知道存不存在一種情況,使得農場的種植作物數量與他記憶中的所有信息吻合。

Input

第一行包括兩個整數n和m,分別表示農場數目和小K記憶中的信息的數目接下來m行:如果每行的第一個數是1,接下來有三個整數a,b,c,表示農場a比農場b至少多種植了c個單位的作物如果每行第一個數是2,接下來有三個整數a,b,c,表示農場a比農場b至多多種植了c個單位的作物如果每行第一個數是3,接下來有兩個整數a,b,表示農場a種植的數量與b一樣。1<=n,m,a,b,c<=10000

Output

如果存在某種情況與小K的記憶吻合,輸出”Yes”,否則輸出”No”

Sample Input

3 3
3 1 2
1 1 3 1
2 2 3 2

Sample Output

Yes
樣例解釋
三個農場種植的數量可以爲(2,2,1)

Source

Kpmcup#0 By Greens

analysis

差分約束。。

先羅列一下約束條件:

1、abca-b\geq c,那麼add(b,a,c)add(b,a,c)(表示aabbcc)。

2、abca-b\leq cbacb\geq a-c,那麼add(a,b,c)add(a,b,-c)(表示bbaacc,注意不能add(b,a,c)add(b,a,c),因爲這和第一個約束衝突,所以反過來就好了)。

3、a==ba==b時,那麼add(a,b,0),add(b,a,0)add(a,b,0),add(b,a,0)(表示aabb相等)。

4、然後從00i=1ni=1\sim n每個點add(0,i,)add(0,i,隨意)(隨便值爲多少,反正只是驗證可行性)。

最後跑spfaspfa(其實是用dfsdfs模擬)求最長路,出現環則輸出NoNo,否則輸出YesYes

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10,inf=23333333;

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}

template<typename T>inline void write(T x)
{
	if (!x) { putchar('0'),putchar('\n'); return ; }
	if (x<0) putchar('-'), x=-x;
	T num=0,ch[20];
	while (x) ch[++num]=x%10+48,x/=10;
	while (num) putchar(ch[num--]);
	putchar('\n');
}

int ver[maxn],edge[maxn],Next[maxn],head[maxn],len;
inline void add(int x,int y,int z)
{
	ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
}

queue<int>q;
int dist[maxn];
bool vis[maxn];

inline bool spfa(int x)
{
	vis[x]=1;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i],z=edge[i];
		if (dist[y]<dist[x]+z)
		{
			dist[y]=dist[x]+z;
			if (vis[y] || !spfa(y)) return 0;
		}
	}
	vis[x]=0;
	return 1;
}

int main()
{
	int n,m;read(n);read(m);
	for (int i=1,opt,a,b,c; i<=m; ++i)
	{
		read(opt);read(a);read(b);
		if (opt==1) read(c),add(b,a,c);
		if (opt==2) read(c),add(a,b,-c);
		if (opt==3) add(a,b,0),add(b,a,0);
	}
	for (int i=1; i<=n; ++i) add(0,i,0),dist[i]=-inf;
	puts(!spfa(0)?"No":"Yes");
	return 0;
}

B. BZOJ 3437: 小P的牧場 斜率優化DP

title

BZOJ 3437
Description

小P在MC裏有n個牧場,自西向東呈一字形排列(自西向東用1…n編號),於是他就煩惱了:爲了控制這n個牧場,他需要在某些牧場上面建立控制站,每個牧場上只能建立一個控制站,每個控制站控制的牧場是它所在的牧場一直到它西邊第一個控制站的所有牧場(它西邊第一個控制站所在的牧場不被控制)(如果它西邊不存在控制站,那麼它控制西邊所有的牧場),每個牧場被控制都需要一定的花費(畢竟在控制站到牧場間修建道路是需要資源的嘛~),而且該花費等於它到控制它的控制站之間的牧場數目(不包括自身,但包括控制站所在牧場)乘上該牧場的放養量,在第i個牧場建立控制站的花費是ai,每個牧場i的放養量是bi,理所當然,小P需要總花費最小,但是小P的智商有點不夠用了,所以這個最小總花費就由你來算出啦。

Input

第一行一個整數 n 表示牧場數目
第二行包括n個整數,第i個整數表示ai
第三行包括n個整數,第i個整數表示bi

Output

只有一行,包括一個整數,表示最小花費

Sample Input

4
2 4 2 4
3 1 4 2

Sample Output

9
樣例解釋
選取牧場1,3,4建立控制站,最小費用爲2+(2+1*1)+4=9。
1<=n<=1000000, 0 < a i ,bi < = 10000

Source

Kpmcup#0 By Greens

analysis

斜率優化。。

f[i]f[i]表示在第ii個位置放置監測器的最小費用,b[i]b[i]表示題目中的bib_i的前綴和,c[i]c[i]表示題目中的biib_i*i的前綴和。

狀態轉移方程:f[i]=min0j&lt;i{f[j]+(b[i]b[j])i(c[i]c[j])}+a[i]f[i]=\min_{0\leq j&lt;i}\left\{f[j]+(b[i]-b[j])*i-(c[i]-c[j])\right\}+a[i]

目標:f[n]f[n]

之後的,套路。。

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}

template<typename T>inline void write(T x)
{
	if (!x) { putchar('0'),putchar('\n'); return ; }
	if (x<0) putchar('-'), x=-x;
	T num=0,ch[20];
	while (x) ch[++num]=x%10+48,x/=10;
	while (num) putchar(ch[num--]);
	putchar('\n');
}

ll a[maxn],b[maxn],c[maxn],f[maxn];
inline double slope(int j,int k)
{
	return (double)(f[j]+c[j]-(f[k]+c[k]))/(double)(b[j]-b[k]);
}

int q[maxn];
int main()
{
	int n;read(n);
	for (int i=1; i<=n; ++i) read(a[i]);
	for (int i=1; i<=n; ++i) read(b[i]),c[i]=c[i-1]+b[i]*i,b[i]+=b[i-1];
	
	int l=1,r=1;
	q[1]=0;
	for (int i=1; i<=n; ++i)
	{
		while (l<r && slope(q[l+1],q[l])<i) ++l;
		f[i]=f[q[l]]+(b[i]-b[q[l]])*i-(c[i]-c[q[l]])+a[i];
		while (l<r && slope(i,q[r])<slope(q[r],q[r-1])) --r;
		q[++r]=i;
	}
	write(f[n]);
	return 0;
}

C. BZOJ 3438: 小M的作物 最小割

title

BZOJ 3438
LUOGU 1361
Description

小M在MC裏開闢了兩塊巨大的耕地A和B(你可以認爲容量是無窮),現在,小P有n中作物的種子,每種作物的種子有1個(就是可以種一棵作物)(用1…n編號),現在,第i種作物種植在A中種植可以獲得ai的收益,在B中種植可以獲得bi的收益,而且,現在還有這麼一種神奇的現象,就是某些作物共同種在一塊耕地中可以獲得額外的收益,小M找到了規則中共有m種作物組合,第i個組合中的作物共同種在A中可以獲得c1i的額外收益,共同總在B中可以獲得c2i的額外收益,所以,小M很快的算出了種植的最大收益,但是他想要考考你,你能回答他這個問題麼?

Input

第一行包括一個整數n
第二行包括n個整數,表示ai第三行包括n個整數,表示bi第四行包括一個整數m接下來m行,
對於接下來的第i行:第一個整數ki,表示第i個作物組合中共有ki種作物,
接下來兩個整數c1i,c2i,接下來ki個整數,表示該組合中的作物編號。輸出格式

Output

只有一行,包括一個整數,表示最大收益

Sample Input

3
4 2 1
2 3 2
1
2 3 2 1 2

Sample Output

11
樣例解釋A耕地種1,2,B耕地種3,收益4+2+3+2=11。
1<=k< n<= 1000,0 < m < = 1000 保證所有數據及結果不超過2*10^9。

Source

Kpmcup#0 By Greens

analysis

網絡流最小割。。

最大收益=總收益-最小損失,最小損失可以通過最小割來求。

設與SS相連表示種在AA,與TT相連表示種在BB

每個作物不能同時種在AABB,應選擇一個割掉,故連邊SiS\to i,容量爲aia_iiTi\to T,容量爲bib_i

對於每個組合,如果這個組合都種在AA,那麼任意一個都不能種在BB,應割掉,故連邊SkaiS\to k_{a_i},容量爲c1ic1_ikaipk_{a_i}\to p,容量爲infinf

都種在B同理。

然後求最小割,用總收益減去最小割即爲答案。

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=3010,maxm=4e6+10,inf=1e9;

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}

template<typename T>inline void write(T x)
{
	if (!x) { putchar('0'),putchar('\n'); return ; }
	if (x<0) putchar('-'), x=-x;
	T num=0,ch[20];
	while (x) ch[++num]=x%10+48,x/=10;
	while (num) putchar(ch[num--]);
	putchar('\n');
}

int ver[maxm],edge[maxm],Next[maxm],head[maxn],len;
inline void add(int x,int y,int z)
{
	ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
	ver[++len]=x,edge[len]=0,Next[len]=head[y],head[y]=len;
}

int s,t;
int dist[maxn];
inline bool bfs()
{
	queue<int>q;
	memset(dist,0,sizeof(dist));
	q.push(s);dist[s]=1;
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=head[x]; i; i=Next[i])
		{
			int y=ver[i];
			if (edge[i] && !dist[y])
			{
				dist[y]=dist[x]+1;
				if (y==t) return 1;
				q.push(y);
			}
		}
	}
	return 0;
}

inline int get(int x,int low)
{
	if (x==t) return low;
	int tmp=low;
	for (int i=head[x]; i; i=Next[i])
	{
		int y=ver[i];
		if (edge[i] && dist[y]==dist[x]+1)
		{
			int a=get(y,min(tmp,edge[i]));
			if (!a) dist[y]=0;
			edge[i]-=a;
			edge[i^1]+=a;
			if (!(tmp-=a)) break;
		}
	}
	return low-tmp;
}

int main()
{
	int n,sum=0,tot;read(n);
	s=0,tot=t=n+1,len=1;
	for (int i=1,a; i<=n; ++i) read(a),add(s,i,a),sum+=a;
	for (int i=1,b; i<=n; ++i) read(b),add(i,t,b),sum+=b;
	int m;read(m);
	for (int i=1,k,c1,c2; i<=m; ++i)
	{
		read(k);read(c1);read(c2);
		add(s,++tot,c1);
		add(++tot,t,c2);
		sum+=c1+c2;
		for (int j=1,x; j<=k; ++j) read(x),add(tot-1,x,inf),add(x,tot,inf);
	}
	while (bfs()) sum-=get(s,inf);
	write(sum);
	return 0;
}

D. BZOJ 3439: Kpm的MC密碼 Trie+可持久化線段樹

title

BZOJ 3439
Description

背景
想Kpm當年爲了防止別人隨便進入他的MC,給他的PC設了各種奇怪的密碼和驗證問題(不要問我他是怎麼設的。。。),於是乎,他現在理所當然地忘記了密碼,只能來解答那些神奇的身份驗證問題了。。。
描述
Kpm當年設下的問題是這樣的:
現在定義這麼一個概念,如果字符串s是字符串c的一個後綴,那麼我們稱c是s的一個kpm串。
系統將隨機生成n個由a…z組成的字符串,由1…n編號(s1,s2…,sn),然後將它們按序告訴你,接下來會給你n個數字,分別爲k1…kn,對於每一個ki,要求你求出列出的n個字符串中所有是si的kpm串的字符串的編號中第ki小的數,如果不存在第ki小的數,則用-1代替。(比如說給出的字符串是cd,abcd,bcd,此時k1=2,那麼”cd”的kpm串有”cd”,”abcd”,”bcd”,編號分別爲1,2,3其中第2小的編號就是2)(PS:如果你能在相當快的時間裏回答完所有n個ki的查詢,那麼你就可以成功幫kpm進入MC啦~~)

Input

第一行一個整數 n 表示字符串的數目
接下來第二行到n+1行總共n行,每行包括一個字符串,第i+1行的字符串表示編號爲i的字符串
接下來包括n行,每行包括一個整數ki,意義如上題所示

Output

包括n行,第i行包括一個整數,表示所有是si的kpm串的字符串的編號中第ki小的數

Sample Input

3
cd
abcd
bcd
2
3
1

Sample Output

2
-1
2
樣例解釋
“cd”的kpm 串有”cd”,”abcd”,”bcd”,編號爲1,2,3,第2小的編號是2,”abcd”的kpm串只有一個,所以第3小的編號不存在,”bcd”的kpm串有”abcd”,”bcd”,第1小的編號就是2。
數據範圍與約定
設所有字符串的總長度爲len
對於100%的數據,1<=n<=100000,0

Source

Kpmcup#0 By Greens

analysis

這道題是真的神仙,反正以我現在的水平得搞半天。。

所以題解參照大佬:FuTaimeng

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}

template<typename T>inline void write(T x)
{
	if (!x) { putchar('0'),putchar('\n'); return ; }
	if (x<0) putchar('-'), x=-x;
	T num=0,ch[20];
	while (x) ch[++num]=x%10+48,x/=10;
	while (num) putchar(ch[num--]);
	putchar('\n');
}

int dfn[maxn],ptr[maxn],ed[maxn],id,root[maxn],re[maxn];;
vector<int>la[maxn];

namespace Trie
{
	int ch[maxn][26],siz=1;
	inline void rever(char s[])
	{
		int len=strlen(s);
		for (int i=0; i<(len>>1); ++i) swap(s[i],s[len-i-1]);
	}
	
	inline void insert(char s[],int pos)
	{
		int x=1;
		for (int i=0; s[i]; ++i)
		{
			if (!ch[x][s[i]-'a']) ch[x][s[i]-'a']=++siz;
			x=ch[x][s[i]-'a'];
		}
		la[x].push_back(pos);
		re[pos]=x;
	}
	
	inline void dfs(int x)
	{
		dfn[x]=++id;
		ptr[dfn[x]]=x;
		for (int i=0; i<26; ++i)
			if (ch[x][i]) dfs(ch[x][i]);
		ed[x]=id;
	}
}

namespace Sgt
{
	int siz,lch[maxn*20],rch[maxn*20],cnt[maxn*20];
	inline void clone(int x,int t)
	{
		lch[x]=lch[t],rch[x]=rch[t],cnt[x]=cnt[t];
	}
	
	inline void insert(int &now,int t,int l,int r,int x)
	{
		now=++siz;
		clone(now,t);
		++cnt[now];
		if (l!=r)
		{
			int mid=(l+r)>>1;
			if (x<=mid) insert(lch[now],lch[t],l,mid,x);
			else insert(rch[now],rch[t],mid+1,r,x);
		}
	}
	
	inline int query(int a,int b,int l,int r,int k)
	{
		if (cnt[b]-cnt[a]<k) return -1;
		if (l==r) return l;
		int mid=(l+r)>>1;
		int lsiz=cnt[lch[b]]-cnt[lch[a]];
		if (k<=lsiz) return query(lch[a],lch[b],l,mid,k);
		else return query(rch[a],rch[b],mid+1,r,k-lsiz);
	}
}

char str[maxn];
int main()
{
	int n;read(n);
	for (int i=1; i<=n; ++i)
	{
		scanf("%s",str);
		Trie::rever(str);
		Trie::insert(str,i);
	}
	Trie::dfs(1);
	
	for (int i=1; i<=id; ++i)
	{
		if (la[ptr[i]].size())
		{
			int pre=root[i-1];
			for (int j=0; j<la[ptr[i]].size(); ++j)
			{
				Sgt::insert(root[i],pre,1,n,la[ptr[i]][j]);
				pre=root[i];
			}
		}
		else root[i]=root[i-1];
	}
	
	for (int i=1; i<=n; ++i)
	{
		int x=re[i];
		int k;read(k);
		write(Sgt::query(root[dfn[x]-1],root[ed[x]],1,n,k));
	}
	return 0;
}

Some thoughts

小k的農場一題,早在很早前都AA過了。。

今天寫的小p的牧場一題,可以得出我的斜率優化DPDP確實是掌握的不錯的,happyhappy!。。

小m的作物一題充分顯示了網絡流這種神仙東西得學,還得學好,誰知道他會不會考。。

對於第四題,隨他去吧,有空再好好學一下TrieTrie和主席樹得嘞。。

最後,感慨一下逍遙嘆。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章