集训3

我直接拍手称寄。

\(\textbf{“我以为T1,T3能场切”}\)

T1:Watching fire is fun.

只有10pts确实挺fun的。

一眼DP,二眼贪心,三眼DP。然后用错的DP转移方程搞了10pts,乐。

一开始打算贪心是因为$ n $给到了\(1500000\),我寻思搞个DP怎么着也得要个\(O(n^2m)\)吧,所以就跳过了,后来还是老老实实写的DP。于是\(Wrong Answer\)力。昨天刚考了斜率优化,今天连个单调队列的毛都没想出来(不过我就算知道要用单调队列,转移方程也不会推,寄)。太惨了。

\(f[i][j]\)表示第\(i\)次烟花位置为\(j\)的最大幸福度,那么转移方程就是:

\[\textstyle f[i][j]=max(f[i-1][k])+Bi[i]-\mid\ Ai[i] - j\quad\mid \]

\[(k\ge\max(1,k-(Ti[i]-Ti[i-1])\times d),k\le\max(n,k+(Ti[i]-Ti[i-1])\times d)) \]

然鹅我们需要小调一下。

  • 空间复杂度问题,由于需要开\(long long\),又观察到每一个\(i\)都要从前一个状态转移过来,所以我们可以使用滚动数组减小空间上的开支。

  • 时间复杂度问题,我们可以对\(f[i-1][k]\)进行单调,维护一个该值递增的队列,需要取出决策时及时排除位置已经不符合条件的选项。

代码:

#include <bits/stdc++.h>
#define int long long
#define Reg register
using namespace std;
const int maxn=151010,maxm=330;
const int inf=9999999999999999;
int n,m,d;
int Ai[maxm],Bi[maxm],Ti[maxm];
int f[maxn],dp[maxn];
int q[maxn];
int ans=-inf;
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar();
	}
	return s*w;
}
signed main(){
	freopen("fire.in","r",stdin);
	freopen("fire.out","w",stdout);
	n=read();m=read();d=read();
	for(Reg int i=1;i<=m;++i) Ai[i]=read(),Bi[i]=read(),Ti[i]=read();
	for(int i=1;i<=m;++i){
		memcpy(dp,f,sizeof(f));
		//滚动数组 
		if(Ti[i]==Ti[i-1]){
			for(int j=1;j<=n;++j)f[j]=dp[j]+Bi[i]-abs(Ai[i]-j);
			continue;
		}
		int l=1,r=0,movs=(Ti[i]-Ti[i-1])*d,k=1;
		for(int j=1;j<=n;++j){
			for(k;k<=min(j+movs,n);k++){
				while(l<=r&&dp[k]>=dp[q[r]]) r--;
				q[++r]=k;
			}
			while(l<=r&&j-movs>q[l]) l++;
			f[j]=dp[q[l]]+Bi[i]-abs(Ai[i]-j);
		}
	}
	for(int i=1;i<=n;++i) ans=max(ans,f[i]);
	cout<<ans;
	return 0;
} 

T2:Performs

唯一一个切了的题,DP方程式贼好想(尼玛又是DP😭👊👊👊),每一个城市每一天的最小花费可以由之前的任一一个城市转移而来,不过需要特殊注意的是起点只能是一城市。循环的问题可以直接暴力解决,毕竟\(K\)不超过\(1000\),或者可以在方程中取模。\(Kamisato_-Ayaka\)巨佬给出了暴搜的好方法。

#include <bits/stdc++.h>
#define ll long long
#define Reg register
using namespace std;
const int maxn=151010,maxm=1100;
const ll inf=999999999;
int n,k;
int fees[15][15][maxm];
int f[15][maxm];
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar();
	}
	return s*w;
}
inline void solve(){
	f[1][0]=0;
	for(int i=2;i<=n;++i){
		if(!fees[1][i][1]) continue;
		f[i][1]=min(f[i][1],f[1][0]+fees[1][i][1]);
		//printf("%lld ",f[i][1]);
	}
	for(int e=2;e<=k;++e){
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				if(e==2&&j==1) continue; 
				if(i==j) continue;
				if(!fees[j][i][e]) continue;
				f[i][e]=min(f[i][e],f[j][e-1]+fees[j][i][e]);
			}
		} 
	}
	if(f[n][k]==inf) printf("0\n");
	else printf("%d\n",f[n][k]);
	return;
}
inline void xuns(int i,int j,int t){
	int segs=2;
	while(t<=k){
		for(int e=t+1;e<=segs*t;e++) fees[i][j][e]=fees[i][j][e-t];
		t=segs*t;segs++;
	}
	return;
}
inline void inputrs(){
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(i==j) continue;
			int xujie=read();
			for(int t=1;t<=xujie;++t) fees[i][j][t]=read();
			xuns(i,j,xujie); 
		}
	}
	for(Reg int i=0;i<=n+1;++i)
		for(Reg int j=0;j<=k+1;j++)
			f[i][j]=inf;
}
int main(){
	freopen("perform.in","r",stdin);
	freopen("perform.out","w",stdout);
	n=read();k=read();
	while(n!=0&&k!=0){
		memset(fees,0,sizeof(fees));
		inputrs();
		solve(); 
		n=read();k=read();
	}
	return 0;
} 

T3:枪战 Maf

题意就是一群人互相打手枪(迫真),决定一个开枪顺序,求出最少死亡人数和最多死亡人数。考场上推了半小时写了半小时,获得了\(0pts\)的好成绩。

这道互相打手枪的题仔细一想有不少有意思的性质:

  • 死亡人数最多时,活下来的只有入度为\(0\)的人和一个独立环里的某一个人。

  • 死亡人数最少时,一个有\(n\)个人的环内活下来的只有\([n/2]\)个人,最多时只有\(1\)个。

  • 对于某一条链,我们选择从入度为\(0\)的人开枪,被打死的人就不能再开枪,而被打死的人想打死的人可以开枪。、

  • 如果一条链指向了某个环,那么死亡人数最多时这个环一个活口都没有了。

真就乱杀

所以我们先用拓扑排序把所有的链删除,再遍历所有的环,同时维护死亡人数最少和最多时的存活人数(死亡人数太难搞)如果这个环在遍历过程中发现被指向或者存在自环(草),那么死亡人数最多时的存活人数就不再加\(1\),死亡人数最少时是节点数除以2。

之前对某一些性质的理解确实有误差,比如如果一条链指向一个环,那么这个环的最小死亡人数会不会被影响之类的,后来才发现自己多虑了。

感谢\(smtwy\)巨佬让我乱杀改掉了这道题。

代码:

#include <bits/stdc++.h>
#define ll long long
#define Reg register
using namespace std; 
const int maxn=1301010,maxm=330;
int n,cnt,head[maxn],axli,inli,tot,in[maxn];
bool sw[maxn],vis[maxn],vis2[maxn];
struct ED{
	int next,to;
}e[maxn*2];
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar();
	}
	return s*w;
}
inline void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
	return;
}

inline void finalbt(int u){
	int laji=0;
	bool fl=0,fl2=0;
	//草 终于明白了 
	while(1){
		if(vis[u]) break;
		//嘶,刚才想错了 
		//vis不会出现环与链交叉的情况
		//但save一定会有,如果说这个环里有save节点,那么这个环就被指过了
		//死亡人数最多时这个环的全部节点都会被打死
		//否则只剩1人 
		vis[u]=1;
		u=e[head[u]].to;
		laji++;
		//环的节点数++ 
		if(sw[u]) fl=1;
	}
	if(laji>1&&!fl) axli++;
	//自环的情况 
	inli+=laji/2; 
	return;
}
inline void tope(){
	queue<int> q;
	for(int i=1;i<=n;++i){
		if(!in[i]) q.push(i),inli++,axli++;
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;
			if(vis[v]) continue;
			vis[v]=1;
			int vv=e[head[v]].to;
			//此时一定能保证能活下来的都已遍历过 
			in[vv]--;sw[vv]=1;
			//把死亡人数最少时的幸存者加入队列 
			if(!in[vv]) inli++,q.push(vv);
		}
	}
	//去链找环 
	for(int i=1;i<=n;++i){
		if(in[i]&&!vis[i]) finalbt(i);
	}
}
int main(){
	freopen("maf.in","r",stdin);
	freopen("maf.out","w",stdout);
	n=read();
	for(Reg int i=1;i<=n;++i){
		int aims=read();add(i,aims);
		in[aims]++;
	}
	tope();
	printf("%d %d",n-inli,n-axli);
	return 0;
} 

T4:翻转游戏

无脑输出\(0\)\(Impossible\)能拿\(50pts\),乐。

真正的暴力搜索,然而咱还是不会(😭)。

来自gtm1514巨佬的代码:

#include <bits/stdc++.h>
#define ll long long
#define Reg register
using namespace std;
const int maxn=151010,maxm=330;
int s[20][20],ans=100,cnt;
int x[20],y[20];
bool fbi,fwi;
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar();
	}
	return s*w;
}
inline void Reverse(int cz){
	int i=x[cz],j=y[cz];
	s[i][j]^=1;
	if(j>1) s[i][j-1]^=1;
	if(j<4) s[i][j+1]^=1;
	if(i>1) s[i-1][j]^=1;
	if(i<4) s[i+1][j]^=1;
	return;
} 
inline bool check(){
	fbi=fwi=0;
	for(int i=1;i<=4;++i){
		for(int j=1;j<=4;++j){
			if(s[i][j]!=s[1][1]) return false;
		}
	}
	return true;
}
void dfs(int now){
	if(check()){
		ans=min(ans,cnt);
		return;
	}
	if(now==17) return;
	for(int i=0;i<=1;++i){
		if(i==1) Reverse(now),cnt++;
		dfs(now+1);
		if(i==1) Reverse(now),cnt--;
	}
	return;
}
int main(){
	freopen("flip.in","r",stdin);
	freopen("flip.out","w",stdout);
	for(int i=1;i<=4;++i){
		for(int j=1;j<=4;++j){
			char ps; scanf(" %c",&ps);
			if(ps=='b') s[i][j]=1,fbi=1;
			else s[i][j]=0,fwi=1;
		}
	}
	for(int i=1;i<=16;++i){
		y[i]=i%4==0?4:i%4;
		x[i]=i%4==0?(i/4):(i/4+1);
	}
	if(!fbi||!fwi) return printf("0"),0;
	else{
		dfs(1);
		if(ans<=16) printf("%d",ans);
		else printf("Impossible");
		//真得好好练暴搜了 
	} 
	return 0;
} 

我写完了。

posted @ 2022-06-07 21:03  Broken_Eclipse  阅读(46)  评论(0)    收藏  举报

Loading