CSP-S考前各類idea題解亂堆

快要考試了我仍是這麼菜。html

已經沒有心思惟護個人博客了。開一篇博文吧。可能會記得很亂。node

這也許是我OI生涯的最後一篇博文了??c++

確定很長很長。數組

不可能的。誰知道何時我心態恢復就把上面兩句話刪掉開始在博客裏各類胡咧咧了。app

好,我心態恢復了。我要寫日記了hhhdom


「$idea$」樹的遍歷

誰也不知道我爲何忽然腦抽去看樹的遍歷。。。ide

實際上並無什麼用處。。。測試

放個連接:樹的遍歷優化

「題解」笨小猴:結論題

賽時$random\_shuffle$原本能$AC$。ui

結果統計總和沒開$long$ $long$,$OJ$測拿了$20$,$lemon$測拿了50。

$lemon$測比$OJ$測高也是創了個記錄。

簡要題解:

把全部卡片按照$A$的大小排序。而後從左到右每兩張卡片分紅一組,咱們選取每組中$B$較大的,再將最後一張卡片選取便可。

證實:最壞的狀況無非兩兩一組中老是$A$較小的那個$B$較大。此時咱們換一種分組方式,把$1$提出來,$2,3$一組,以此類推就全是$A$較大的那個$B$較大了。

因此沒有$-1$狀況。

代碼:

這個是$random\_shuffle$的。

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,a[200005],b[200005],Suma,Sumb,ans[100005],tim,id[200005];
inline void dfs(int k,int gt,int suma,int sumb)
{
	if(gt==n+1)
	{
		if(suma>Suma-suma&&sumb>Sumb-sumb)
		{
			for(rint i=1;i<=gt;++i)
				printf("%d\n",ans[i]);
			exit(0);
		}
		return ;
	}
	if(k==2*n+2)return ;
	dfs(k+1,gt,suma,sumb);ans[gt+1]=k;
	dfs(k+1,gt+1,suma+a[k],sumb+b[k]);
	return ;
}
signed main()
{
	freopen("grandmaster.in","r",stdin);
	freopen("grandmaster.out","w",stdout);
	srand(time(NULL));
	tim=clock();read(n);
	for(rint i=1;i<=2*n+1;++i)
	{
		read(a[i]),read(b[i]);
		Suma+=a[i],Sumb+=b[i],id[i]=i;
	}
	if(n<=10){dfs(1,0,0,0);puts("-1");return 0;}
	while(1)
	{
		if(clock()-tim>940000)break;
		random_shuffle(id+1,id+2*n+1);
		int sa=0,sb=0;
		for(rint i=1;i<=n+1;++i)
			sa+=a[id[i]],sb+=b[id[i]];
		if(sa>Suma-sa&&sb>Sumb-sb)
		{
			for(rint i=1;i<=n+1;++i)
				printf("%d\n",id[i]);
			return 0;
		}
	}
	puts("-1");return 0;
}

這個是正解。

#include<bits/stdc++.h>
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,ans[100005],tim,id[200005];
struct node{int a,b,id;}pi[200005];
inline bool cmp(node A,node B){return A.a<B.a;}
int main()
{
	freopen("grandmaster.in","r",stdin);
	freopen("grandmaster.out","w",stdout);
	read(n);
	for(rint i=1;i<=2*n+1;++i)
		read(pi[i].a),read(pi[i].b),pi[i].id=i;
	sort(pi+1,pi+2*n+1+1,cmp);
	for(rint i=1;i<=n;++i)
	{
		if(pi[(i<<1)-1].b<=pi[i<<1].b)
			printf("%d\n",pi[i<<1].id);
		else printf("%d\n",pi[(i<<1)-1].id);
	}
	printf("%d\n",pi[2*n+1].id);
	return 0;
}

「題解」開心的金明:貪心+模擬

我沒看出來這道題和金明有半毛錢關係。。。辦公司的不是小木麼。。。

簡要題解:

貪心。不想證實。

電腦能造多少造多少,大不了不賣都扔掉。賣電腦的時候選成本最小的賣掉,成本大的能夠退掉。

而後就沒了。維護兩個小根堆來回捯完事了。

注意別一個電腦一個電腦的模擬,會T60。一組一組得處理。

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
const int N=50004,inf=0x7fffffff;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,ci[N],di[N],pi[N],mi[N],ei[N],ri[N],gi[N];
int yt[N],ccf[N],ycb[N],ck[N],now,sum,ans;
struct node{int wi,num;};
bool operator < (node A,node B){
	return A.wi>B.wi;
}
priority_queue<node> q[2];
signed main()
{
	freopen("happy.in","r",stdin);
	freopen("happy.out","w",stdout);
	read(n);ci[0]=inf;memset(ycb,0x7f,sizeof(ycb));
	for(rint i=1;i<=n;++i)read(ci[i]),read(di[i]),read(mi[i]),read(pi[i]);
	for(rint i=1;i<n;++i)read(ei[i]),read(ri[i]),read(gi[i]),ccf[i+1]=ccf[i]+ri[i];
	for(rint i=1;i<=n;++i)
	{
		if(ci[yt[i-1]]+ccf[i]-ccf[yt[i-1]]<ycb[i])
			ycb[i]=ci[yt[i-1]]+ccf[i]-ccf[yt[i-1]],yt[i]=yt[i-1];
		if(ci[i]<ycb[i])yt[i]=i,ycb[i]=ci[i];
	}
	for(rint i=1;i<=n;++i,now^=1)
	{
		if(sum+pi[i]<di[i])return puts("-1"),0;
		q[now].push((node){ycb[i]+mi[i],pi[i]});
		int lin=0;
		while(lin+q[now].top().num<=di[i])
			ans+=q[now].top().num*q[now].top().wi,lin+=q[now].top().num,q[now].pop();
		if(lin<di[i])
		{
			int dwi=q[now].top().wi,dnum=q[now].top().num,lst=di[i]-lin;
			ans+=lst*dwi;q[now].pop();q[now].push((node){dwi,dnum-lst});
		}
		while(q[!now].size())q[!now].pop();
		lin=sum=0;
		while((!q[now].empty())&&lin+q[now].top().num<=ei[i])
		{
			q[!now].push((node){q[now].top().wi+gi[i],q[now].top().num});
			lin+=q[now].top().num,sum+=q[now].top().num,q[now].pop();
		}
		if(lin<ei[i]&&(!q[now].empty()))
		{
			int dwi=q[now].top().wi,dnum=q[now].top().num,lst=ei[i]-lin;
			q[!now].push((node){dwi+gi[i],lst});sum+=lst;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

「題解」陶陶摘蘋果:線段樹+二分+單調棧

 咱們熟悉陶陶摘蘋果,蘋果摘陶陶,此次陶陶再次來摘蘋果了,是排序仍是貪心,是迪利克雷卷積仍是快速傅里葉變換。

我說我沒看出來線段樹維護單調棧你信麼。。。考場上本身yy了一個$O(nlog^2n)$的作法。

簡要題解:

能夠發現,對於一個位置的改變,最終答案能夠拆成兩部分:該位置前的和該位置後的。

咱們能夠考慮先在本來的序列上跑一遍答案,記錄下來答案路徑,而後用線段樹記錄區間最大值和區間最大值第一次出現的位置,還要跑一遍單調棧維護出全部節點到最後的上升序列長度。

對於這個位置之前的部分,有一個顯然的性質是這一部分的區間最大值第一次出現的位置必定在答案路徑上。

我更改的是它後面的值,不會對它的答案路徑產生影響。因此直接繼承過來到它的答案路徑長度。

若是我當前更改的值要大於前一部分的最大值,則當前節點也在本次更改的答案路徑上。ans++,區間最大值要更新爲當前修改值。

對於後一部分,二分找到第一個比當前值要大的值的位置,答案加上這個位置到最後的上升序列長度便可。

代碼:

#include<bits/stdc++.h>
#define rint register int
using namespace std;
const int inf=0x7fffffff;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,m,h[100005];
int t[100005<<5],loc[100005<<5];
int sta[100005],tp,bop[100005],len[100005];
int ky[100005],rd[100005],cnt;
inline void update(int k)
{
	t[k]=max(t[k<<1],t[k<<1|1]);
	if(t[k]==t[k<<1])loc[k]=loc[k<<1];
	else loc[k]=loc[k<<1|1];
}
inline void build(int k,int l,int r)
{
	if(l==r){t[k]=h[l],loc[k]=l;return ;}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	update(k);return ;
}
inline void change(int k,int l,int r,int p,int dat)
{
	if(l==r){t[k]=dat;return ;}
	int mid=(l+r)>>1;
	if(p<=mid)change(k<<1,l,mid,p,dat);
	else change(k<<1|1,mid+1,r,p,dat);
	update(k);return ;
}
inline void query(int k,int l,int r,int L,int R,int &res,int &lc)
{
	if(R<L)return res=0,lc=0,void();
	if(L<=l&&r<=R){if(t[k]>res)res=t[k],lc=loc[k];return ;}
	int mid=(l+r)>>1;
	if(L<=mid)query(k<<1,l,mid,L,R,res,lc);
	if(R>mid)query(k<<1|1,mid+1,r,L,R,res,lc);
	return ;
}
inline int ef(int st,int dat)
{
	int l=st,r=n+1;
	while(l<r)
	{
		int mid=(l+r)>>1,res=0,lc=0;
		query(1,1,n,st,mid,res,lc);
		if(res<=dat)l=mid+1;
		else r=mid;
	}
	return len[l];
}
int main()
{
	freopen("TaoPApp.in","r",stdin);
	freopen("TaoPApp.out","w",stdout);
	read(n),read(m);h[0]=inf;
	for(rint i=1;i<=n;++i)read(h[i]);
	if(n<=5000&&m<=5000)
	{
		for(rint i=1,p,hp,tp,lin,ans;i<=m;++i)
		{
			read(p),read(hp);tp=h[p],h[p]=hp;
			lin=ans=0;
			for(rint j=1;j<=n;++j)
				if(h[j]>lin)lin=h[j],ans++;
			printf("%d\n",ans);h[p]=tp;
		}
		return 0;
	}
	build(1,1,n);
	for(rint i=1;i<=n;++i)
	{
		while(h[sta[tp]]<h[i])bop[sta[tp--]]=i;
		sta[++tp]=i;
	}
	for(rint i=n;i>=1;--i)len[i]=len[bop[i]]+1;
	int mxp=0;
	for(rint i=1;i<=n;++i)
		if(h[i]>mxp)mxp=h[i],rd[++cnt]=i,ky[i]=cnt;
	for(rint i=1,p,hp,ans;i<=m;++i)
	{
		read(p),read(hp);ans=0;
		int res=0,lc=0,tag=hp;
		query(1,1,n,1,p-1,res,lc);
		ans=ky[lc];
		(res>=hp)?tag=res:ans++;
		ans+=ef(p+1,tag);
		printf("%d\n",ans);
	}
	return 0;
}

「題解」求和:等差數列求和公式

$sb$等差數列求和公式,取模後再除以2掛了$70$分神白名列。

改了半天沒改過,請$Wang\_hecao$大佬幫我$debug$了一下。

還有C承擔兩次除法這種操做??

簡要題解:

$sb$等差數列求和公式。

取模後不能作除法運算。取模後不能作除法運算。取模後不能作除法運算。

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int l,u,r,d,mod,A,B,C,ans,flag=0;
inline int qpow(int a,int b)
{
	int sum=1;
	while(b){if(b&1)sum=sum*a%mod;a=a*a%mod;b>>=1;}
	return sum;
}
inline int multi(int a,int b)
{
	int sum=0;
	while(b){if(b&1)sum=(sum+a)%mod;a=(a+a)%mod;b>>=1;}
	return sum;
}
signed main()
{
	freopen("sum.in","r",stdin);
	freopen("sum.out","w",stdout);
	read(l),read(u),read(r),read(d),read(mod);
	A=r-l+1;B=d-u+1;C=l+u+r+d-2;
	if(A%2==0)A/=2,flag++;
	if(B%2==0&&!flag)B/=2,flag++;
	if(C%2==0&&!flag)C/=2,flag++;
	A%=mod;B%=mod;C%=mod;
	ans=multi(multi(A,B),C)%mod;
	printf("%lld\n",ans);
	return 0;
}

「題解」分組配對:倍增+二分

賽時開了4個手寫棧,最壞時間複雜度$O(n^2)$水了75分,一直以爲帶上二分是$O(n^2log)$的結果人家有85-90的好成績。。

感謝lyl大佬帶我從新認識二分。這個世界上沒有普適的二分板子.jpg。

簡要題解:

倍增斷定範圍,二分肯定具體分界點,能夠保證時間複雜度穩定爲$O(nlog^2n)$

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,m,a[500005],b[500005],sum;
int male[500005],female[500005];
inline bool check(int lb,int rb)
{
	int res=0;
	for(rint i=lb;i<=rb;++i)male[i]=a[i],female[i]=b[i];
	sort(male+lb,male+rb+1);sort(female+lb,female+rb+1);
	for(rint i=lb;i<=rb;++i)res+=male[i]*female[i];
	return res<m;
}
inline int work(int st,int l,int r)
{
	while(l<r)
	{
		int mid=(l+r+1)>>1;
		if(check(st,mid))l=mid;
		else r=mid-1;
	}
	return l;
}
signed main()
{
	freopen("pair.in","r",stdin);
	freopen("pair.out","w",stdout);
	read(n),read(m);
	for(rint i=1;i<=n;++i)read(a[i]);
	for(rint i=1;i<=n;++i)read(b[i]);
	int st=1,en=1;
	while(st<=n)
	{
		int pos=1;
		for(;st+pos<n;pos<<=1)
			if(!check(st,st+pos))break;
		st=work(st,st+(pos>>1),min(st+pos,n))+1,sum++;
	}
	printf("%lld\n",sum);
	return 0;
}

「題解」真相:預處理

賽時一直考慮如何正着推理而後就死了。

簡要題解:

一個比較暴力的想法是枚舉說「真」話的人數,枚舉每個意淫預言家,能夠直接知道$ta$說的「真」$or$「假」,由此向上(逆時針)能夠一直推到上一個預言家。

優化能夠考慮直接預處理每一個預言家到上一個預言家的區間,處理內容爲當前預言家說話爲「真」時區間內說真話的人數和當前預言家說話爲「假」時區間內說真話的人數。而後按預言家的預言內容存儲人數。因爲同時最多隻有一種預言家說的是真話,因此能夠$O(1)$查詢這種預言家說真話時說真話的人數,因而咱們先本身預言全部預言家說的都是錯的,直接統計全部預言家均說假話時說真話的人數,當查詢每一種預言家的時候直接減去這種預言家說假話時說真話的人數,加上這種預言家說真話時說真話的人數便可。

代碼:

#include<bits/stdc++.h>
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int T,n,cnt[2][1000006],as[1000006];char ch[3];bool te=0;
vector <int> v;
struct node{int opt,wi;}h[1000006];
inline int lst(rint x){return (x==1)?n:x-1;}
int main()
{
	freopen("truth.in","r",stdin);
	freopen("truth.out","w",stdout);
	read(T);
	while(T--)
	{
		read(n);te=0;v.clear();
		for(rint i=1;i<=n;++i)
		{
			scanf("%s",ch);
			switch(ch[0])
			{
				case '+':h[i].opt=1;break;
				case '-':h[i].opt=2;break;
				case '$':
					h[i].opt=3;read(h[i].wi);te=1;
					v.push_back(i);break;
			}
		}
		if(!te)
		{
			int tag=0;
			for(rint i=1;i<=n;++i)
			{
				if(tag==0&&h[i].opt==1){tag=0;continue;}
				if(tag==0&&h[i].opt==2){tag=1;continue;}
				if(tag==1&&h[i].opt==1){tag=1;continue;}
				if(tag==1&&h[i].opt==2){tag=0;continue;}
			}
			if(tag==0){puts("consistent");continue;}
			else{puts("inconsistent");continue;}
		}
		else
		{
			for(rint i=0;i<=n;++i)cnt[0][i]=cnt[1][i]=0;
			int ans=0;
			for(rint j=0;j<v.size();++j)
			{
				int sum=0;
				int lin=v[j],tag=0;
				int loc=lin,lt=lst(lin);
				while(h[lt].opt!=3)
				{
					if(tag==0&&h[lt].opt==1)tag=0;
					else if(tag==0&&h[lt].opt==2)tag=1,sum++;
					else if(tag==1&&h[lt].opt==1)tag=1,sum++;
					else if(tag==1&&h[lt].opt==2)tag=0;
					loc=lt,lt=lst(loc);
				}
				cnt[0][h[lin].wi]+=sum;
			}
			for(rint j=0;j<v.size();++j)
			{
				int sum=1;
				int lin=v[j],tag=1;
				int loc=lin,lt=lst(lin);
				while(h[lt].opt!=3)
				{
					if(tag==0&&h[lt].opt==1)tag=0;
					else if(tag==0&&h[lt].opt==2)tag=1,sum++;
					else if(tag==1&&h[lt].opt==1)tag=1,sum++;
					else if(tag==1&&h[lt].opt==2)tag=0;
					loc=lt,lt=lst(loc);
				}
				cnt[1][h[lin].wi]+=sum;
			}
			for(rint i=0;i<=n;++i)ans+=cnt[0][i];
			for(rint i=0;i<=n;++i)
			{
				ans-=cnt[0][i];ans+=cnt[1][i];
				if(ans==i){puts("consistent");goto skyhecao;}
				ans+=cnt[0][i];ans-=cnt[1][i];
			}
			puts("inconsistent");
skyhecao:	continue;
		}
	}
}
/*
3
3
+
+
$ 3
3
+
-
$ 3
1
-
*/

「題解」格式化:排序題、貪心

簡要題解:

因此我不會作排序題。賽時xjb寫了三個排序加上$random_shuffle$水到了80分hhh。

正解排序方式:分個類,先格式化有收益的,有收益的按格式化以前的空間排序,有損耗的按照格式化以後的空間排序。先後一致的放中間。

代碼:

#include<bits/stdc++.h>
#define int long long
#define Online_Judge
//#define dp
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,ans=0x7fffffffffffffff,res=0,space=0,tim=0;
struct node{int st,en;}pan[2000006];
inline bool cmp(node A,node B)
{
	if(A.st<A.en&&B.st<B.en)return A.st<B.st;
	if(A.st>A.en&&B.st>B.en)return A.en>B.en;
	if(A.st>A.en&&B.st<B.en)return false;
	if(A.st<A.en&&B.st>B.en)return true;
	if(A.st<A.en&&B.st==B.en)return true;
	if(A.st>A.en&&B.st==B.en)return false;
	if(A.st==A.en&&B.st<B.en)return false;
	if(A.st==A.en&&B.st>B.en)return true;
	return A.en+A.st<B.en+B.st;
}
signed main()
{
#ifdef dp
	freopen("4.in","r",stdin);
	freopen("3.out","w",stdout);
#endif
#ifdef Online_Judge
	freopen("reformat.in","r",stdin);
	freopen("reformat.out","w",stdout);
#endif
	read(n);
	for(rint i=1;i<=n;++i)
		read(pan[i].st),read(pan[i].en);
	res=0;space=0;
	sort(pan+1,pan+n+1,cmp);
	for(rint i=1;i<=n;++i)
	{
		if(pan[i].st>space)
		{
			res+=pan[i].st-space;
			space=pan[i].en;
		}
		else
		{
			space-=pan[i].st;
			space+=pan[i].en;
		}
	}
	ans=min(ans,res);
	printf("%lld\n",ans);
	return 0;
}

「$idea$」$\mathcal{Chebyshev}$ $\mathcal{Distance}$ $\&$ $\mathcal{Manhattan}$ $\mathcal{Distance}$

切比雪夫距離與曼哈頓距離關係密切,由於它們能夠相互轉化。

切比雪夫轉曼哈頓:$(x,y)->(\frac{x+y}2 , \frac{x-y}2)$

曼哈頓轉切比雪夫:$(x,y)->(x+y,x-y)$

例題:$[TJOI2013]$松鼠聚會

題目大意:給出一個座標系上的一些點爲松鼠的家,定義點與點之間的距離爲切比雪夫距離,求全部松鼠都到一個松鼠家裏去所走的最小路徑長度和。數據範圍$n \leq 1e5 , |x|,|y| \leq 1e9$

題解:

先將原座標系上的點$(x,y)$轉化爲$(x,y)->(\frac{x+y}2 , \frac{x-y}2)$

原圖上的切比雪夫距離隨即轉化爲新圖的曼哈頓距離。

答案就變成了$min{\sum\limits_{j=1}^n|x_i-x_j| + \sum\limits_{j=1}^n|y_i-y_j|}$

此時咱們考慮分別對$x$和$y$排序,能夠直接把絕對值去掉。

化簡可得

$min\{(i-1)*x_i - \sum\limits_{j=1}^{i-1} x_j + \sum\limits_{j=i+1}^n x_j - (n-i)*x_i + i*y_i - \sum\limits_{j=1}^i y_j + \sum\limits_{j=i+1}^n y_j - (n-i)*y_i\}$

前綴和優化便可。

p.s.上面這個柿子我手打的,有什麼錯你們必定要指出來呀……

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,ans=0x7fffffffffffffff;
int sxfrt[100005],sxbak[100005],syfrt[100005],sybak[100005],inx[100005],iny[100005];
int x[100005],y[100005];
signed main()
{
//	freopen("1.in","r",stdin);
//	freopen("1.out","w",stdout);
	read(n);
	for(rint i=1,stx,sty;i<=n;++i)
	{
		read(stx),read(sty);
		x[i]=inx[i]=stx+sty;
		y[i]=iny[i]=stx-sty;
	}
	sort(x+1,x+n+1);
	sort(y+1,y+n+1);
	for(rint i=1;i<=n;++i)
	{
		sxfrt[i]=sxfrt[i-1]+x[i],
		syfrt[i]=syfrt[i-1]+y[i];
	}
	for(rint i=n;i>=1;--i)
	{
		sxbak[i]=sxbak[i+1]+x[i],
		sybak[i]=sybak[i+1]+y[i];
	}
	for(rint i=1;i<=n;++i)
	{
		int res=0;
		int res1=lower_bound(x+1,x+n+1,inx[i])-x;
		int res2=lower_bound(y+1,y+n+1,iny[i])-y;
		res+=(res1-1)*inx[i]-sxfrt[res1-1]+sxbak[res1+1]-(n-res1)*inx[i];
		res+=(res2-1)*iny[i]-syfrt[res2-1]+sybak[res2+1]-(n-res2)*iny[i];
		ans=min(ans,res);
	}
	printf("%lld\n",ans>>1);
	return 0;
}

「非知識性失分」賽後收文件前胡亂更改文件

額,這個正式比賽應該不會存在問題。不過畢竟是校內比賽。。。

說一下痛苦的過程:

賽時的文件有人的沒有收上來,老師就從新收了一遍。

然而收上去的是我賽後寫的一個用來測試long double的代碼。。。

爲了圖方便我直接在主目錄的文件上更改的。因而60->0。


「題解」二叉搜索樹:區間dp+剪枝

發現O(n^3)的暴力dp仍是挺好想的。然而賽時我就是被模擬矇蔽了雙眼。

簡要題解:

區間dp的通常形式:設$f_{i,j}$表示只考慮區間$i$到$j$的答案。

轉移只須要在$i$到$j$之間枚舉轉移點$k$,使得$k$爲區間$i$到$j$造成二叉搜索樹的根節點。

轉移方程:$f_{i,j}=f_{i,k-1}+f_{k+1,j}+\sum\limits_{l=i}^j x_i$。

優化:概括證實可得上述轉移存在決策單調性,因此咱們記錄一下每一個區間的上一個轉移點,枚舉轉移點的時候在$[i,j-1]$和$[i+1,j]$的轉移點之間枚舉便可。

中間出了一點小$bug$,就是說枚舉$k$的時候會出現$k+1>n$的狀況,不處理會$w70$,初始化一下不合法區間就能夠了。

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
inline void read(int &a)
{
	a=0;int b=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,x[5003],f[5003][5003],sum[5003],g[5003][5003];
signed main()
{
	read(n);memset(f,0x3f,sizeof(f));
	for(rint i=1;i<=n;++i)
	{
		read(x[i]),sum[i]=sum[i-1]+x[i];
		f[i][i]=x[i],g[i][i]=i;
		for(rint j=0;j<i;++j)f[i][j]=0;
	}
	for(rint i=0;i<=n+1;++i) f[n+1][i]=0;
	for(rint i=2;i<=n;++i)for(rint j=1;j+i-1<=n;++j)
		for(rint k=max(g[j][j+i-2],j);k<=max(g[j+1][j+i-1],j);++k)
			if(f[j][k-1]+f[k+1][i+j-1]+(sum[i+j-1]-sum[j-1])<f[j][i+j-1])
				f[j][i+j-1]=f[j][k-1]+f[k+1][i+j-1]+(sum[i+j-1]-sum[j-1]),g[j][i+j-1]=k;
	printf("%lld\n",f[1][n]);
}

 「題解」數列:exgcd

窩栽了。說真的我看出來$exgcd$了。也寫出來了。可是不會用是硬傷。

$exgcd$主要用來解二元一次方程$ax+by=gcd(a,b)$的一組特解。根據這一組特解咱們能夠推廣到$ax+by=c$中。根據裴蜀定理,當且僅當$gcd(a,b)|c$時,方程$ax+by=c$有解。

exgcd的簡單證實:

  對於方程$ax+by=gcd(a,b)$,令$a=b,b=a%b$,則有$b*x+a\%b*y=gcd(b,a\%b)$

  又$gcd(a,b)=gcd(b,a\%b)$,因此可得

  $a*x+b*y=b*x+a\%b*b=b*x+(a-a/b*b)*y=a*y+b*(x-a/b*y)$

  得證。

代碼實現:

int exgcd(rint a,rint b,rint &x,rint &y)
{
	if(!b)
	{
		x=1;y=0;
		return a;
	}
	rint res=exgcd(b,a%b,x,y);
	rint t=x;x=y;y=t-a/b*x;
	return res;
}

簡要題解:

關於數列這道題,咱們須要運用$exgcd$先行解出一組特解。咱們最終的目的是使得$abs(x)+abs(y)$最小,咱們假設令$a<b$,那麼答案必定在$x$取得正數最小值或負數最大值時取得。若是$a>b$,$swap$後再進行解決。

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
inline void read(rint &a)
{
	a=0;rint b=1;register char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,A,B,a[100005],dist[200005],fx[4],ans;
bool vis[200005];
int exgcd(rint a,rint b,rint &x,rint &y)
{
	if(!b){x=1;y=0;return a;}
	rint res=exgcd(b,a%b,x,y);
	rint t=x;x=y;y=t-a/b*x;
	return res;
}
signed main()
{
//	freopen("array.in","r",stdin);
//	freopen("1.in","r",stdin);
//	freopen("2.out","w",stdout);
	read(n),read(A),read(B);
	if(A>B)swap(A,B);rint x,y,gcd;
	gcd=exgcd(A,B,x,y);
	for(rint i=1;i<=n;++i)
	{
		read(a[i]);
		if(a[i]%gcd){puts("-1");return 0;}
		rint lx=x*a[i]/gcd,ly=y*a[i]/gcd;
//		cout<<"lx:"<<lx<<" ly:"<<ly<<endl;
		rint la=A/gcd,lb=B/gcd;
		if(lx>0)
		{
			rint tag=lx/lb,res=0x7fffffffffffffff;
			res=min(abs(lx-tag*lb)+abs(ly+tag*la),res);
			res=min(abs(lx-(tag+1)*lb)+abs(ly+(tag+1)*la),res);
			res=min(abs(lx-(tag-1)*lb)+abs(ly+(tag-1)*la),res);
			ans+=res;
		}
		else
		{
			rint tag=(-lx)/lb,res=0x7fffffffffffffff;
			res=min(abs(lx+tag*lb)+abs(ly-tag*la),res);
			res=min(abs(lx+(tag+1)*lb)+abs(ly-(tag+1)*la),res);
			res=min(abs(lx+(tag-1)*lb)+abs(ly-(tag-1)*la),res);
			ans+=res;
		}
	}
	printf("%lld\n",ans);
}

「題解」最小距離:多源最短路

說實話,歷來沒有接觸過多源最短路。(或者是接觸過我忘了??)

簡要題解:

spfa最開始將全部源點都塞進隊列,跑的時候記錄一下每個節點是被哪一個源點更新的就完了。

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
const int N=200005,M=400005,inf=0x3f3f3f3f3f3f3f3f;
inline void read(rint &a)
{
	a=0;rint b=1;register char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,m,p,v[M],w[M],nxt[M],first[N],tot;
int id[N],rk[N],ans[N],dist[N],gx[N],st[N],en[N],wi[N];
bool vis[N];
queue <int> q;
inline void build_line(int uu,int vv,int ww)
{
	v[++tot]=vv,w[tot]=ww;
	nxt[tot]=first[uu],first[uu]=tot;
}
inline void spfa()
{
	for(rint i=1;i<=n;++i)
		dist[i]=inf,vis[i]=0;
	for(rint i=1;i<=p;++i)
		gx[id[i]]=i,q.push(id[i]),dist[id[i]]=0;
	while(!q.empty())
	{
		int x=q.front();q.pop();
		for(rint i=first[x];i;i=nxt[i])
		{
			int y=v[i];
			if(dist[x]+w[i]<dist[y])
			{
				dist[y]=w[i]+dist[x],gx[y]=gx[x];
				if(!vis[y])vis[y]=1,q.push(y);
			}
		}
		vis[x]=0;
	}
	return ;
}
signed main()
{
//	freopen("distance.in","r",stdin);
	read(n),read(m),read(p);
	for(rint i=1;i<=p;++i)
		read(id[i]),rk[id[i]]=i,ans[i]=inf;
	for(rint i=1,ST,EN,WE;i<=m;++i)
	{
		read(ST),read(EN),read(WE);st[i]=ST,en[i]=EN,wi[i]=WE;
		build_line(ST,EN,WE),build_line(EN,ST,WE);
	}
	spfa();
//	for(rint i=1;i<=n;++i)
//		cout<<dist[i]<<' '<<gx[i]<<endl;
//	cout<<"----------------------"<<endl;
	for(rint i=1;i<=m;++i)
	{
		if(gx[st[i]]!=gx[en[i]])
		{
			int res=dist[st[i]]+dist[en[i]]+wi[i];
//			cout<<i<<' '<<st[i]<<' '<<en[i]<<' '<<res<<endl;
			ans[gx[st[i]]]=min(ans[gx[st[i]]],res);
			ans[gx[en[i]]]=min(ans[gx[en[i]]],res);
		}
	}
	for(rint i=1;i<=p;++i)
		printf("%lld ",ans[i]);puts("");
	return 0;
}
/*
5 6 3
2 4 5
1 2 4
1 3 1
1 4 1
1 5 4
2 3 1
3 4 3
*/

「題解」數對:線段樹優化dp

簡要題解:

對於隨機排部求最優解,考慮最優排部。

若是$a_i<b_j$而且$b_i<a_j$,則此時必定是$i$在$j$前更優。

(由於產生貢獻當且僅當$a_i \leq b_j$,上述狀況$i$在前能夠產生貢獻,$j$在前不能產生貢獻。)

因此按照$a+b$排序後就是隊長快跑的原題了。

以前有一個錯解想法,具體是說按照$a_i$的大小建一棵樹狀數組,按$b_i$查詢,按$a_i$修改。

然然後來證僞了。

如上圖,咱們會用$A$區域的值去更新$i$,此時$a_j$的值被更新到了$a_i$身上。

而後會用$B$區域的值去更新$k$,此時會用$a_j$的值去更新$k$。

等於說咱們間接使用$a_j$去更新了$b_k$,而這樣的更新是不合法的!

因此在更新的過程當中須要分兩種狀況。對於小於$a_i$的部分,直接取出最大值更新$a_i$單點便可。對於$a_i$和$b_i$之間的部分,咱們須要用$i$的貢獻去更新他們。線段樹實現查詢區間最大值、區間加、單點修改便可。

$debug$的時候出了點小問題,線段樹下傳的節點編號參數我一直喜歡寫$k$,結構體內部維護的權值我也喜歡寫$k$。原本嘛,由於命名空間不一樣不會出鍋。我也以爲這沒啥事。然而此次出鍋了。我在$pushdown$中寫了這樣一句話:$t[k<<1|1],k+=lin$,中間的點寫成了逗號。因爲傳進去的參也是$k$,因此編譯沒有報錯。而後我就死了。因此之後仍是要儘可能改掉這個寫法,傳個$x$吧。

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
using namespace std;
inline void read(rint &a)
{
	a=0;rint b=1;register char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,lsh[200005],cnt,tot;
struct node{int k,lz;}t[1000006];
struct node2{int a,b,w;}p[100005];
inline bool cmp(node2 A,node2 B){return A.a+A.b<B.a+B.b;}
inline void pushdown(int k)
{
	if(!t[k].lz)return ;
	int lin=t[k].lz;t[k].lz=0;
	t[k<<1].k+=lin,t[k<<1].lz+=lin;
	t[k<<1|1].k+=lin,t[k<<1|1].lz+=lin;
	return; 
}
inline void update(int k){t[k].k=max(t[k<<1].k,t[k<<1|1].k);}
inline void add(int k,int l,int r,int L,int R,int dat)
{
	if(L<=l&&r<=R)
	{
		t[k].k+=dat;
		t[k].lz+=dat;
		return ;
	}
	pushdown(k);int mid=(l+r)>>1;
	if(L<=mid)add(k<<1,l,mid,L,R,dat);
	if(R>mid)add(k<<1|1,mid+1,r,L,R,dat);
	update(k);return ;
}
inline void change(int k,int l,int r,int p,int dat)
{
	if(l==r)
	{
		t[k].k=max(t[k].k,dat);
		return ;
	}
	pushdown(k);int mid=(l+r)>>1;
	if(p<=mid)change(k<<1,l,mid,p,dat);
	else change(k<<1|1,mid+1,r,p,dat);
	update(k);return ;
}
inline int query(int k,int l,int r,int L,int R)
{
	if(L>R)return 0;
	if(L<=l&&r<=R)return t[k].k;
	pushdown(k);int mid=(l+r)>>1,res=0;
	if(L<=mid)res=max(query(k<<1,l,mid,L,R),res);
	if(R>mid)res=max(query(k<<1|1,mid+1,r,L,R),res);
	update(k);
	return res;
}
signed main()
{
	read(n);
	for(rint i=1;i<=n;++i)
	{
		read(p[i].a),read(p[i].b),read(p[i].w);
		lsh[++tot]=p[i].a,lsh[++tot]=p[i].b;
	}
	sort(lsh+1,lsh+tot+1);
	cnt=unique(lsh+1,lsh+tot+1)-lsh-1;
	for(rint i=1;i<=n;++i)
	{
		p[i].a=lower_bound(lsh+1,lsh+cnt+1,p[i].a)-lsh;
		p[i].b=lower_bound(lsh+1,lsh+cnt+1,p[i].b)-lsh;
	}
	sort(p+1,p+n+1,cmp);
	for(rint i=1;i<=n;++i)
	{
		if(p[i].b>p[i].a)
		{
			int res=query(1,1,cnt,1,p[i].a)+p[i].w;
			change(1,1,cnt,p[i].a,res);
			add(1,1,cnt,p[i].a+1,p[i].b,p[i].w);
		}
		else
		{
			int res=query(1,1,cnt,1,p[i].b)+p[i].w;
			change(1,1,cnt,p[i].a,res);
		}
	}
	printf("%lld\n",t[1].k);
	return 0;
}

「題解」序列:三維偏序問題

這東西……我就算不能秒掉也不至於盯着發呆無所適從吧……我認可題目把我騙住了。仍是實力不行沒有看出來。

簡要題解:

對$a$和$b$分別求前綴和,問題轉化爲了保證$a_{l-1} \leq a_r$而且$b_{l-1} \leq b_r$,求$r-l+1$的最大值。

考慮先進行離散化,而後按$suma$進行排序,而後就轉化爲了二維偏序問題。

順序掃並將下標按$sumb$加入樹狀數組中,每次查詢當前最小下標便可。

調代碼的過程出現了一些小問題。答案減一其實沒有必要。注意一下問題的轉化:

$\sum\limits_{i=l}^r a_i \geq 0$而且$\sum\limits_{i=l}^r b_i \geq 0$求$r-l+1$的最大值

$ \Leftrightarrow $

$suma_{l-1} \leq suma_r$而且$sumb_{l-1} \leq sumb_r$求$r-l+1$的最大值。

因此咱們查出來的下標實際上是$l-1$,咱們此時求的$id-query()$實際上是$r-(l-1)=r-l+1$,已經沒有必要額外加一了。

因此我加了個一反而多此一舉。

代碼:

#include<bits/stdc++.h>
#define int long long
#define rint register int
#define lowbit(A) A&-A
#define inf 0x7fffffffffffffff
using namespace std;
inline void read(rint &a)
{
	a=0;rint b=1;register char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')b=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){a=(a<<3)+(a<<1)+ch-'0';ch=getchar();}
	a=a*b;return ;
}
int n,a[500005],b[500005],ans,t[1000005],lsh[1000005],tot,cnt;
struct node{int a,b,id;}sum[500005];
inline bool cmp(node A,node B){return A.a<B.a;}
inline void add(int x,int dat){for(;x<=cnt;x+=lowbit(x))t[x]=min(t[x],dat);return ;}
inline int query(int x){int res=inf;for(;x;x-=lowbit(x))res=min(t[x],res);return res;}
signed main()
{
//	freopen("sequence.in","r",stdin);
	read(n);memset(t,0x7f,sizeof(t));
	for(rint i=1;i<=n;++i)read(a[i]),sum[i].a=sum[i-1].a+a[i],lsh[++tot]=sum[i].a;
	sum[0].a=sum[0].b=0,sum[0].id=0;lsh[++tot]=0;
	for(rint i=1;i<=n;++i)read(b[i]),sum[i].b=sum[i-1].b+b[i],lsh[++tot]=sum[i].b,sum[i].id=i;
	sort(lsh+1,lsh+tot+1);cnt=unique(lsh+1,lsh+tot+1)-lsh-1;
	for(rint i=0;i<=n;++i)sum[i].a=lower_bound(lsh+1,lsh+cnt+1,sum[i].a)-lsh;
	for(rint i=0;i<=n;++i)sum[i].b=lower_bound(lsh+1,lsh+cnt+1,sum[i].b)-lsh;
	sort(sum,sum+n+1,cmp);
	for(rint i=0;i<=n;++i)ans=max(ans,sum[i].id-query(sum[i].b)+1),add(sum[i].b,sum[i].id);
	printf("%lld\n",ans-1);
	return 0;
}
相關文章
相關標籤/搜索