考试7.15

烽火传递(signal.cpp)

【题目描述】

烽火台是重要的军事防御设施,一般建在交通要道或险要处。一旦有军情发
生,则白天用浓烟,晚上用火光传递军情。
在某两个城市之间有n座烽火台,每个烽火台发出信号都有一定的代价。为
了使情报准确传递,在连续m个烽火台中至少要有一个发出信号。
现在输入n和每个烽火台发出信号的代价ai,请计算总共最少需多少的代价
才能实现两城市之间准确传递情报。

【输入格式】

第一行是用空格隔开两个数 n 和 m;
第二行使用空格隔开的 n 个整数,表示每个烽火台发出信号的代价。

【输出格式】

输出仅一个整数,表示最小代价。

【数据规模与约定】

对于 30% 的数据,n,m≤10;

对于 50% 的数据,n,m≤1000;

对于 100%的数据,n,m≤\(2*10^5\),对所有数据有 1<=ai<=1000。


【Solution】

单调优先队列优化dp

考试时把弹出队尾的f[i]打成f[q[i]],只拿了10分,亏大了。

上代码啦!
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m;
int a[N],q[N],f[N];//以i为结尾的最小

inline int read() {
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

int main() {
//	freopen("signal.in","r",stdin);
//	freopen("signal.out","w",stdout);
	n=read(),m=read();
	for(int i=1; i<=n; i++) {
		a[i]=read();
	}
	memset(f,0x3f,sizeof f);
	int l=1,r=1;
	f[0]=0;
	for(int i=1; i<=n; i++) {
		while(l<r&&q[l]+m<i)l++;
		f[i]=f[q[l]]+a[i];
// cout<<l<<" "<<r<<" "<<i<<" :"<<f[i]<<endl;
		while(l<r&&f[q[r]]>f[i])r--;
		q[++r]=i;
	}
	int ans=0x3f3f3f3f;
	for(int i=n-m+1; i<=n; i++) {
// cout<<i<<" "<<f[i]<<endl;
		ans=min(ans,f[i]);
	}
	cout<<ans<<endl;
	return 0;
}

小R与回家(home.cpp)

【题目描述】

小R在火星玩累了,打算回家, 这是一个时空旅行的故事。
小R要从火星(编号为1)回到地球(编号为n),宇宙中一共有n个星球,
星球之间有一些航线,但是有些星球间的航线可能导致时间倒流,并且星球
之间的航线并不保证可逆(即a到b的时间和b到a的时间不一定相同)。时空旅行
要求不能在出发时间之前到达最终目的地,否则会导致时空错乱,危及生命。
小R的飞船上有速度调节装置,可以用来改变星球间飞行的时间,即可以将
整次时空旅行中两两星球之间的时间增加或减少同一个整数。
小R很想家,你能帮助他求出最快回家的时间。

【输入格式】

输入文件包含多组数据,第1个数为T,表示数据组数。
对于每组数据,输入第1行为两个正整数n,m,为星球的个数和星球间的路
线数。
接下来m行,每行三个整数i,j和t, 表示由星球i到星球j飞行的时间为t。
由i到j最多只会有一条飞行线路。

【输出格式】

输出文件共T行,每组数据输出一行。
如果可以通过调节速度调节器完成任务,则输出一个非负整数,表示由星球1到星球n的最短时间(注意最短时间要大于或者等于0)。
如果不能由火星回到地球,则输出-1。

【数据规模与约定】

对于20% 的数据,n<=10,t>=0

另有20% 的数据,保证所有星球出度不超过1

另有20% 的数据,-100<=t<=100;

对于100%的数据:T<=10,n<=100,m<=n*(n-1),-100000<=t<=100000。


【Solution】

二分求增加减少值,每次跑一遍最短路,找最小非负整数

k\t 0 1 2
7 -4 3 10
3 -3 0 3
2 -3 -1 1

例如上图所求应为第4列第4行的1

  1. 考虑二分答案。因为图中含有负边权,所以用 spfa 来统计最短路的答案。

  2. 把不会到达的点处理掉然后对答案进行二分;

  3. 用check(x) 判断 x 是否可以作为边权的调整值

  4. 用count(i,x) 函数,扫描图中的负环,判断当前调整值情况下是否存在负环

  5. 如果没有负环,则用spfa(x) 计算在调整值为 x 的情况下的最短路;(因为图中含有负边权)

Code
#include<bits/stdc++.h>
using namespace std;

const int inf=0x3f3f3f3f;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

const int N=105;
const int M=1e4+5;

int T;
int n,m;
//建图 
int h[N],ver[M],nex[M],val[M],tot;
void add(int x,int y,int z)
{
	ver[++tot]=y,val[tot]=z,nex[tot]=h[x],h[x]=tot;
}

int d[N],vis[N],k[N];

void init()
{
	tot=1;
	memset(h,0,sizeof h);
	memset(ver,0,sizeof ver);
	memset(nex,0,sizeof nex);
	memset(val,0,sizeof val);
}

void spfa(int t)
{
	memset(d,0x3f,sizeof d);
	memset(vis,0,sizeof vis);
	d[1]=0,vis[1]=1;
	queue<int>q;
	q.push(1);
	while(q.size())
	{
		int u=q.front();q.pop();
		for(int i=h[u];i;i=nex[i])
		{
			int v=ver[i];
			if(!k[v])continue;
			if(d[v]>d[u]+val[i]+t)
			{
				d[v]=d[u]+val[i]+t;
				if(!vis[v])q.push(v),vis[v]=1;
			}
		}
		vis[u]=0;
	}
}

void dfs(int x)
{
	if(vis[x])return ;
	vis[x]=1;
	for(int i=h[x];i;i=nex[i])
		dfs(ver[i]);
}

int count(int x,int t)
{
	vis[x]=1;
	for(int i=h[x],v;i;i=nex[i])
	{
		if(!k[v=ver[i]])continue;
		if(d[v]>d[x]+val[i]+t)
		{
			if(vis[v])return 1;
			d[v]=d[x]+val[i]+t;
			if(count(v,t))return 1;
		}
	}
	vis[x]=0;
	return 0;
}

bool check(int x)
{
	for(int i=1;i<=n;i++)
	{
		if(!k[i])continue;
		memset(vis,0,sizeof vis);
		memset(d,0x3f,sizeof d);
		if(count(i,x))return false;
	}
	spfa(x);
	if(d[n]>=0) return true;
	else return false;
}

int main()
{
	T=read();
	while(T--)
	{
		n=read();m=read();
		int u,v,z;init();//预处理 
		for(int i=1;i<=m;i++)
		{
			u=read(),v=read(),z=read();
			add(u,v,z);
		}
		memset(k,0x3f,sizeof k);
		memset(vis,0,sizeof vis);
		dfs(1);
		for(int i=1;i<=n;i++)
		{
			if(!vis[i])k[i]=0;
		}
		for(int i=1;i<=n;i++)
		{
			if(!k[i])continue;
			memset(vis,0,sizeof vis);
			dfs(i);
			if(!vis[n])k[i]=0;
		}
		//二分求增减值
		int l=-100000,r=100000;
		int ans=inf;
		while(l<r)
		{
			int mid=(l+r)>>1;
//			cout<<mid<<" :"<<l<<" "<<r<<" "<<d[n]<<endl;
			if(check(mid))
			{
				ans=d[n];
				r=mid;
			}
			else l=mid+1;
		}
		if(ans>=inf)printf("%d\n",-1);
		else cout<<ans<<endl;
	}
	return 0;
}

疯狂的火神(crazy.cpp)

【题目描述】

火神为了检验zone的力量,他决定单挑n个人。
由于火神训练时间有限,最多只有t分钟,所以他可以选择一部分人来单挑,
由于有丽子的帮助,他得到了每个人特定的价值,每个人的价值由一个三元组
(a,b,c)组成,表示如果火神在第x分钟单挑这个人,他就会得到a-b*x的经验值,
并且他需要c分钟来打倒这个人。
现在火神想知道,他最多可以得到多少经验值,由于火神本来就很笨,进入
zone的疯狂的火神就更笨了,所以他希望你来帮他计算出他最多可以得到多少经
验值。

【输入格式】

第一行一个正整数T,表示数据组数
对于每组数据,第一行为两个正整数n和t,表示跟火神单挑的人的个数和火
神的训练时间。
下面n行,每行三个正整数Ai,Bi,Ci,表示每个人的价值,含义见题目。

【输出格式】

对于每组数据输出一行一个整数表示火神最多能得到多少经验值

【数据规模与约定】

对于20% 的数据:1≤n≤10

对于50% 的数据:1≤n≤18

对于100%的数据:1≤n≤1000,1≤t≤3000,1≤Ci≤t,Ai≤\(10^6\)

保证n>200的数据组数不超过五组,其他的数据组数不超过10组,保证每个
人贡献的经验值到训练结束都不会变成负数。


【Solution】

\(b_i和c_i的比值排序\),01背包

考试的时候知道是dp,但完全想不到

改的时候,没有比较最大值,交了好几发才过,叹气.jpg(果然,我太废了

代码又来啦!
#include<bits/stdc++.h>
using namespace std;

int T;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

const int N=1e3+5;

int n,m,f[N*3];
struct ww{
	int a,b,c;
	double d;
}pl[N];

bool cmp(ww x, ww y)
{
	return x.d>y.d;
}

int main()
{
	T=read();
	while(T--)
	{
		n=read(),m=read();
		for(int i=1;i<=n;i++)
		{
			pl[i].a=read(),pl[i].b=read();
			pl[i].c=read();pl[i].d=1.0*pl[i].b/pl[i].c;
		}
		sort(pl+1,pl+n+1,cmp);
		memset(f,0,sizeof f);
		for(int i=1;i<=n;i++)
		{
			for(int j=m;j>=pl[i].c;j--)
			{
				f[j]=max(f[j],f[j-pl[i].c]+pl[i].a-pl[i].b*j);
			}
		}
		int ans=0;
		for(int i=1;i<=m;i++)ans=max(ans,f[i]);
		cout<<ans<<endl;
	}
	return 0;
}

火神的鱼(fish.cpp)

【题目描述】

火神最爱的就是吃鱼了,所以某一天他来到了一个池塘边捕鱼。池塘可以看
成一个二维的平面,而他的渔网可以看成一个与坐标轴平行的矩形。
池塘里的鱼不停地在水中游动,可以看成一些点。有的时候会有鱼游进渔网,
有的时候也会有鱼游出渔网。所以火神不知道什么时候收网才可以抓住最多的鱼,
现在他寻求你的帮助。
他对池塘里的每条鱼都给予了一个标号,分别从1到n标号,n表示池塘里鱼
的总数。鱼的游动可以概括为两个动作:
1 l r d:表示标号在[l,r]这个区间内的鱼向x轴正方向游动了d个单位长度。
2 l r d:表示标号在[l,r]这个区间内的鱼向y轴正方向游动了d个单位长度。
在某些时刻火神会询问你,现在有多少条他关心的鱼在渔网内(边界上的也
算),请你来帮助他吧。

【输入格式】

第一行包含一个整数T,表示测试数据组数,对于每组测试数据:
第一行包含一个整数n表示鱼的总数。
第二行包含四个整数x1,y1,x2,y2,表示渔网的左下角坐标和右上角坐标。
接下来n行每行两个整数xi,yi,表示标号为i的鱼初始时刻的坐标。
接下来一行包含一个整数m,表示后面的事件数目。
接下来的m行,每行为以下三种类型的一种:
1 l r d:表示标号在[l,r]这个区间内的鱼向x轴正方向游动了d个单位长度。
2 l r d:表示标号在[l,r]这个区间内的鱼向y轴正方向游动了d个单位长度。
3 l r:表示询问现在标号在[l,r]这个区间内的鱼有多少在渔网内。

【输出格式】

对于每组数据的每个询问,输出一个整数表示对应的答案。

【数据规模与约定】

对于30%的数据1≤n,m≤1000

对于100%的数据1≤T≤10,1≤n,m≤30000,1≤l≤r≤n. 1≤d≤\(10^9\),x1≤x2,y1≤y2。

保证任意时刻所有涉及的坐标值在[\(−10^9,10^9\)]范围内。


【Solution】

线段树

posted @ 2022-07-15 19:18  两只风小鱼  阅读(52)  评论(0)    收藏  举报