加载中…

返回上一页

6 . 7

6月7日

首先,还是老一套:

#   用  户  名   Watching Fireworks is Fun Perform巡回演出 枪战Maf 翻转游戏 总分
12
1Liu 10
00:43:41
80
02:24:38
10
03:47:11
100
03:52:12
200
03:52:12

啊……这这这...确实有些许令人不满意.

T1

Watching Fireworks is Fun
CodeForces372C
题目描述
一个节日将在一个镇的主街道上举行. 街道被分为\(n\)段,从左到右编号为\(1\)\(n\),每个相邻部分之间的距离为\(1\).

节日里在主街道有\(m\)个烟花要放,第\(i(1≤i≤m)\)次烟花将在\(t_i\)时在\(a_i\)段燃放. 如果您在第i次发射时处于\(x(1≤x≤n)\),您将获得幸福值\(b_i - | a_i - x |\) (请注意,幸福价值可能是负值)

您可以以单位时间间隔移动长度为\(d\)个长度单位,但禁止离开主要街道. 此外,在初始时刻(时间等于\(1\))你可以在街道的任意部分,并希望最大化从观看烟花获得的幸福总和. 找到最大的总幸福.

请注意,两个或多个烟花可以同时发射

输入格式
第一行包含三个整数\(n\) , \(m\) , \(d\) (\(1\leq n\leq150000; 1\leq m\leq300; 1\leq d\leq n\))

接下来 \(m\) 行每行包含三个整数$ a_{i} $ , $ b_{i} $ , $ t_{i} $ ($ 1\leq a_{i}\leq n; 1\leq b_{i}\leq 10^{9}; 1\leq t_{i}\leq 10^{9} $).第 i 行描述第 i 次烟花发射

确保满足条件$ t_{i}\leq t_{i+1} $(\(1\leq i<m\)

输出格式
输出一个整数,表示观看所有烟花获得的最大幸福总和

样例

样例输入1:

50 3 1
49 1 1
26 1 4
6 1 10
样例输出1:
-31
样例输入2:
10 2 1
1 1000 4
9 1000 4
样例输出2:
1992

题解:

  1. 观察到 m 并不大,所以 t 的数量也就是有限制的,考虑求最优方案,那么不难想到Dp去做,设定Dp[i][j]表示在位置 i,时刻 j 的时候获得的最优的欢乐值.
    那么我们不难想到其状态转移方程:
    Dp[i][j]=Dp[x][j-1](x 和 i 之间的差距要在时间内走的到才能转移)+ 当前时刻能够获得的价值 summ;
    那么我们不难想到用线段树加速优化,但是这道题卡了Log(可以自己尝试一下,没准可行).........................
    观察到转移的时候,Dp[i][j]转移新获得的价值只和当前位子 j 有关,所以我们能够用单调队列优化.
  2. 对于单调队列能够确定决策单调性,但是两个方向不能同时进行,所以我们分两个方向两次去做就行了.
    数组LL太大,爆内存,要用滚动数组优化= =
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define maxn 5001
#define rll rg ll
using namespace std;
ll n,m,d;
ll dp[2][maxn*30];
ll a[maxn],b[maxn],t[maxn],ans=-0x7fffffffffffffff;
deque<ll> q;
int main()
{
	ios::sync_with_stdio(0);
	cin>>n>>m>>d;
	for(rll i=1;i<=m;i++) cin>>a[i]>>b[i]>>t[i];
	for(rll i=1;i<=m;i++)
	{
		memcpy(dp[1],dp[0],sizeof(dp[0]));
		while(!q.empty()) q.pop_front();
		if(t[i]==t[1])
		{
			for(rll j=1;j<=n;j++)
				dp[0][j]=dp[1][j]+b[i]-abs(a[i]-j);
			continue;
		}
		for(rll j=1,w=1,ls=0;j<=n;j++)//w:当前位置
		{
			while(w<=min(d*(t[i]-t[i-1])+j,n))
			{
				while(!q.empty()&&dp[1][w]>=dp[1][q.back()])
					q.pop_back();
				q.push_back(w++);
			}
			while(!q.empty()&&j-d*(t[i]-t[i-1])>q.front())
				ls=q.front(),q.pop_front();
			dp[0][j]=dp[1][q.empty()?ls:q.front()]+b[i]-abs(a[i]-j);
		}
	}
	for(rll i=1;i<=n;i++) ans=max(ans,dp[0][i]);
	cout<<ans;
	return 0;
}

T2

Perform巡回演出
cogs 125
题目描述
Flute 市的 Phlharmoniker 乐团 2000 年准备到 Harp 市做一次大型演出 , 本着普及古典音乐的目的 , 乐团指挥 L.Y.M 准备在到达 Harp 市之前先在周围一些小城市作一段时间的巡回演出 , 此后的几天里 , 音乐家们将每天搭乘一个航班从一个城市飞到另一个城市 , 最后才到达目的地 Harp 市 ( 乐团可多次在同一城市演出 ).

由于航线的费用和班次每天都在变 , 城市和城市之间都有一份循环的航班表 , 每一时间 , 每一方向 , 航班表循环的周期都可能不同.现要求寻找一张花费费用最小的演出表.

输入格式
输入包括若干个场景 . 每个场景的描述由一对整数 n(2<=n<=10) 和 k(1<=k<=1000) 开始 , 音乐家们要在这 n 个城市作巡回演出 , 城市用 1..n 标号 , 其中 1 是起点 Flute 市 ,n 是终点 Harp 市 , 接下来有 n*(n-1) 份航班表 , 一份航班表一行 , 描述每对城市之间的航线和价格 , 第一组 n-1 份航班表对应从城市 1 到其他城市 (2,3,...n) 的航班 , 接下的 n-1 行是从城市 2 到其他城市 (1,3,4...n) 的航班 , 如此下去.

每份航班又一个整数 d(1<=d<=30) 开始 , 表示航班表循环的周期 , 接下来的 d 个非负整数表示 1,2...d 天对应的两个城市的航班的价格 , 价格为零表示那天两个城市之间没有航班.例如 "3 75 0 80" 表示第一天机票价格是 75KOI, 第二天没有航班 , 第三天的机票是 80KOI, 然后循环 : 第四天又是 75KOI, 第五天没有航班 , 如此循环.输入由 n=k=0 的场景结束.

输出格式
对每个场景如果乐团可能从城市 1 出发 , 每天都要飞往另一个城市 , 最后 ( 经过 k 天 ) 抵达城市 n, 则输出这 k 个航班价格之和的最小值.如果不可能存在这样的巡回演出路线 , 输出 0.

样例

样例输入

3 6
2 130 150
3 75 0 80
7 120 110 0 100 110 120 0
4 60 70 60 50
3 0 135 140
2 70 80
2 3
2 0 70
1 80
0 0

样例输出

460
0

题解:
乐团第 x 天在第 y 个地方的最优值只与第 x-1 天有关,符合动态规划的无后效性原则,即只与上一个状态相关联.

而某一天 x 航班价格为 t[(x-1)%c[l][j].num+1].

我们用天数和地点来规划,a[i][j]表示第 i 天到达第 j 个城市的最优值.

struct 数组 c[11][11] 用来描述航班情况.

第 i 天从城市 l 到城市 j 的航班价格是 c[l][j].t[(i-1)%c[l][j].num+1]

动态转移方程是:a[i][j]=Min{a[i-1][l]+c[l][j].t[(i-1)%c[l][j].num+1]},其中1<=l<=n,且 c[l][j].t[(i-1)%c[l][j].num+1]!=0.

即考虑每一天都要乘飞机,就可以枚举每一天,再在每一天内枚举每一个城市,更新它能到达的城市以及时间,转移状态到下一天.

#include<bits/stdc++.h>
#define ll long long
#define rg register
#define maxn 10001
#define rll rg ll
using namespace std;
ll n,k;
ll a[11][11][maxn];
ll dp[11][maxn];
int main()
{
	ios::sync_with_stdio(0);
	while(cin>>n>>k && n && k)
	{
		memset(a,0,sizeof(a));
		memset(dp,0x3f,sizeof(dp));
		for(rll i=1;i<=n;i++)
		{
			for(rll j=1;j<=n;j++)
			{//i:当前城市 j:飞往城市 l:几天,价格
				if(j==i) continue;
				cin>>a[i][j][0];
				for(rll l=1;l<=a[i][j][0];l++)
					cin>>a[i][j][l];
			}
		}
		for(rll i=2;i<=n;i++)
			if((!a[1][i][0])||(!a[1][i][1])) continue;
			else dp[i][1]=a[1][i][1];//,cout<<dp[i][1]<<' ';
		for(rll i=2;i<=k+1;i++)//时间
		{
			for(rll ls=1;ls<=n;ls++)//出发
			{
				for(rll to=1;to<=n;to++)//到达
				{
					if(ls==to) continue;
					if((!a[ls][to][0])||(!a[ls][to][(i-1)%a[ls][to][0]+1]) continue;
					dp[to][i]=min(dp[to][i],dp[ls][i-1]+a[ls][to][(i-1)%a[ls][to][0]+1]);
				}
			}
		}
		if(dp[n][k]<4557430888798830399) cout<<dp[n][k]<<endl;
		else cout<<0<<endl;
	}
	return 0;
}

T3

枪战Maf
bzoj 1124
题目描述
\(n\) 个人,用\(1 \sim n\) 进行编号,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开枪.因此,对于不同的开枪顺序,最后死的人也不同.
输入格式
输入\(n\)人数 \(n<1000000\)
接下来 \(n\) 个数,依次为每个人的 \(aim\)
输出格式
输出只有一行,共两个数,为要求最后死亡数目的最小和最大可能.
样例
样例输入

8
2 3 2 2 6 7 8 5
样例输出
3 5
**题解:**

首先让我们先明确几条性质来帮助我们做题:

  1. 对于每个连通图,他至少有一个环或者一个人自杀.缩完点之后它是树或树林.证明:假设我们已经输入了n个人,先忽略一下第n个人要杀谁,如果之前没有人自杀或环的话它一定是一棵树,因为有n个点和n-1条边,那么第n个人指向哪里就很关键了,他指向每一个其他人都会形成一个环,而指向他自己又是自杀,所以,第一条get.
  2. 每一个没人杀的人都会存活,每一个自杀的人最终都会死.不解释.
  3. 每个人对于答案的影响不在于他被谁杀掉,而在于他杀掉谁,世界不关心你说了什么,世界只关心你做了什么.
  4. 一个环如果没人干预,那么它最少死\(\frac{n}{2}\)个人,向下取整.也就是一个人如果不杀人就只能被杀死,对于奇数个点的环你杀了人也可能被杀死.最多就是总人数-1.
  5. 一个环如果有人干预,那么它最多就是全部被杀,最小在下面说.
  6. 如果一个人没死,那么他指向的那个人就一定会死,如果指向那个人的人都死了,那么他就可以活下来,这一点在环中同样适用.

为了方便,我们直接去求活着的人数,反正人不是活着就是死了废话.

  先说死的人最少,那么入度为0的人一定活下来了,因此我们用队列慢慢往上爬即可,只要队列中的人他所杀的人要杀的人没人杀他了,那么他也可以入队.至于没人指向的环嘛,上面说了.

  死的最多的人就是让人们从树顶从上往下开枪,直到叶子节点所以答案就为缩完点后入度为0的点.

  我打完之后只过了一个点,因为这题有两个坑点,至少对我来说是这样的.

  第一,指向这个点的点可以有好几个,但这个点的出度只有一个,虽然是废话,但在你判断谁存活的时候需要先判断一下,你目前队首的这个点所指向的点是否已经被杀掉,否则错炸了.

  第二,也是卡住了无数英雄好汉的点,如何判断某个环是否已被用队列访问,开个bool记录看似容易,然而在哪里打标记就是大坑了,对于WA的童鞋们可以试一下这个点:

      4

      2 3 2 3

  在这个点中,环中每个人都死了,但如果你在将某个元素塞进队列时才将它打上标记你会发现实际已经访问了的环并未被打上标记,因此,正解是每个被塞进队列的人他所指向的人和他所指向的人所指向的人都要被打上标记.

懒了
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define maxn 1000001
#define rll rg ll
using namespace std;
ll n,aim[maxn];
ll fl[maxn];
bool d[maxn],ext[maxn];
ll cnt,minnum;
queue<ll> q;
int main()
{
	ios::sync_with_stdio(0);
	cin>>n;
	for(rll i=1;i<=n;i++) cin>>aim[i],fl[aim[i]]++;
	for(rll i=1;i<=n;i++)
		if(!fl[i]) q.push(i),cnt++,minnum++;
	for(rll i=1;i<=cnt;i++)
	{
		rll tmp=q.front();q.pop();
		if(d[aim[tmp]]) continue;
		d[aim[tmp]]=1;
		fl[aim[aim[tmp]]]--;
		ext[aim[aim[tmp]]]=1;
		if(!fl[aim[aim[tmp]]])
			q.push(aim[aim[tmp]]),cnt++;
	}
	for(rll i=1;i<=n;i++)
	{
		if(!d[i] && fl[i])
		{
			rll tmp=i,tot=0;
			rg bool flag=0;
			while(!d[tmp])
			{
				tot++;
				if(ext[tmp]) flag=1;
				d[tmp]=1;
				tmp=aim[tmp];
			}
			if(!flag && tot>1) minnum++;
			cnt+=(tot>>1);
		}
	}
	cout<<n-cnt<<' '<<n-minnum;
	return 0;
}

T4

翻转游戏
poj 1753
题目描述

  • 翻转游戏是在一个 \(4×4\) 的正方形上进行的,在正方形的 \(16\) 个格上每个格子都放着一个双面的物件.每个物件的两个面,一面是白色,另一面是黑色,每个物件要么白色朝上,要么黑色朝上,每一次你只能翻 \(1\) 个物件,从而由黑到白的改变这些物件上面的颜色,反之亦然.每一轮被选择翻转的物件遵循以下规则:

    1. \(16\) 个物件中任选一个.
    2. 翻转所选择的物件的同时,所有与它相邻的左方物件、右方物件、上方物件和下方物件(如果有的话),都要跟着翻转.
    • 以下为例:
      bwbw
      wwww
      bbwb
      bwwb

      • 这里 \(b\) 表示该格子放的物件黑色面朝上、\(w\) 表示该格子放的物件白色朝上.如果我们选择翻转第三行的第一个物件,那么格子状态将变为:

      bwbw
      bwww
      wwwb
      wwwb

  • 游戏的目标是翻转到所有的物件白色朝上或黑色朝上.你的任务就是写一个程序来求最少的翻转次数来实现这一目标.

输入格式
输入文件包含 \(4\) 行,每行 \(4\) 个字符,每个字符 \(w\)\(b\) 表示游戏开始时格子上物件的状态.
输出格式
输出文件仅一个整数,即从给定状态到实现这一任务的最少翻转次数.如果给定的状态就已经实现了目标就输出 0,如果不可能实现目标就输出: \(Impossible\).
样例
样例输入

bwwb
bbwb
bwwb
bwww
样例输出
4

题解:
把黑白当作01,把图每一行相接形成一行,转换成01串后化成十进制.
用BFS对每一个点进行翻转,从一个图可以拓展出16种状态,接着继续拓展.
而对每一个点的翻转操作,可以利用位运算中的异或运算.
依照参考程序中的做法,对左上角的翻转操作就是异或19,对右下角的翻转操作就是异或51200.
计算方法就是把要翻转的位置当作是1,别的位置当作是0,按照开头的方法转化成01串后转化成10进制.

//明显的搜索
#include<bits/stdc++.h>
#define ll int
#define rg register
#define rll rg ll
using namespace std;
string s;
ll a[5][5],ans=0x7fffffff;
inline bool check()
{
	for(rll i=1;i<=4;i++)
		for(rll j=1;j<=4;j++)
			if(a[i][j]!=a[1][1])
				return 0;
	return 1;
}
inline void reverse(ll x,ll y)
{
	a[x][y]^=1;
	if(x+1>=1&&x+1<=4&&y>=1&&y<=4) a[x+1][y]^=1;
	if(x-1>=1&&x-1<=4&&y>=1&&y<=4) a[x-1][y]^=1;
	if(x>=1&&x<=4&&y+1>=1&&y+1<=4) a[x][y+1]^=1;
	if(x>=1&&x<=4&&y-1>=1&&y-1<=4) a[x][y-1]^=1;
}
inline void dfs(ll x,ll y,ll num)
{
	if(check())
	{
		ans=min(ans,num);
		return;
	}
	if(x<1||x>4||y<1||y>4) return;
	dfs(x+y/4,y%4+1,num);
	reverse(x,y);
	dfs(x+y/4,y%4+1,num+1);
	reverse(x,y);//一个简简单单的回溯
}
int main()
{
	ios::sync_with_stdio(0);
	for(rll i=1;i<=4;i++)
	{
		cin>>s;
		for(rll j=0;j<4;j++)
			a[i][j+1]=(s[j]=='w');
	}
	if(check())
	{
		cout<<0;
		return 0;
	}
	// bfs();
    //为什么不用广搜了
    //因为广搜可能无法全面操作到棋盘,导致部分地方没有被访问到
	dfs(1,1,0);
	if(ans==2147483647) cout<<"Impossible";
	else cout<<ans;
	return 0;
}
posted @ 2022-06-07 16:54  1Liu  阅读(496)  评论(0)    收藏  举报