JXJJOI2018_三题

这次比赛的话其实还挺满意的,虽然T1 20pts(指的是分数,考试时知道有坑但是考完都没找到的我就知道切不掉这题qwq),T3爆零是在意料之外,不过其实T2贪心能切掉也是意料之外的,所以作此判断。当然在队里rank3(其实也是队里倒数rank3 qwq)这种事也还不错吧(毕竟我上一场队测爆零辣)。

这次的三题总体来说难度个人感觉其实是在noip DAY1之下一点点的。我觉得T1坑题,其实最多最多算个普及/提高-,T2应该大概是普及+/提高的难度,然后T3的树形dp也不难想,所以就给了个提高+/省选-就差不多了吧qwq,因为这几年noip一年比一年毒瘤了嘛qwq。

来来来看题辣。(・∀・)ノ゙

T1_market:带坑简单题。

题目描述

某天Lemon去超市买柠檬,他发现货架上有N个柠檬,每个柠檬都有一个重量Wi和价格Ci。

Lemon身上只带了S元钱,因此他想要买一个价格不超过S的柠檬回家,另外,他希望他买的那个柠檬的性价比尽量高。

性价比的定义是重量除以价格,即第i个柠檬的性价比是Wi/Ci。你的任务是告诉Lemon,他应该买第几个柠檬。

输入输出格式

输入格式

输入文件第一行包含两个正整数N,S。

输入文件第2~N+1行,每行包含两个正整数Wi、Ci,第i+1行的数表示第i个柠檬的重量和价格。

输出格式

输输出文件第一行仅包含一个数K,表示购买第K只柠檬能使Lemon在可以接受的价格内获得最高的性价比。题目保证答案唯一。

样例

INPUT

4 15
4 8
4 10
8 10
10000 20

OUTPUT

3

HINT

样例解释 Sample Explanation:
第1只柠檬重量为4,价格为8,性价比为4/8=0.5;
第2只柠檬重量为4,价格为10,性价比为4/10=0.4;
第3只柠檬重量为8,价格为10,性加比为8/10=0.8;
第4只柠檬重量为10000,价格为20,性价比为10000/20=500,但Lemon只带了15元,无法购买这只柠檬。
因此Lemon的最佳选择是第3只柠檬。
数据范围 Data Range:
对于100%的数据,满足:0<n≤100000;0<s≤109;0<wi、ci≤109;
n,s,w,c均为整数。

SOLUTION

带坑简单题。

这题把坑拿掉顶多就是普及-的难度。

如果数据是\(10^9\)的数量级的话就必须考虑一下精度问题,因为我们一般使用的double类型的有效位数为15位,所以考虑简单转化:$$\frac{w_i}{c_i}>\frac{w_{rec}}{c_{rec}}$$等效于$$w_i\cdot c_{rec}>w_{rec}\cdot c_i$$

这样的话就只要改成long long就好了,以乘代除来保证精度的技巧以前也出现过,并没有重视,所以应该是一个比较好的教训了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
typedef long long LL;
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-48;ch=getchar();}
	return x*f;}
int n,S,ans=0;
LL recw=0,recc=0;
inline LL gcd(LL x,LL y) {return (!y)?x:gcd(y,x%y);}
int main(){
	//freopen("market.in","r",stdin);
	//freopen("market.out","w",stdout);
	int i,j;
	n=read();S=read();
	for (i=1;i<=n;++i) {LL w=read(),c=read();if (c>S) continue;
		LL g=gcd(w,c);w/=g;c/=g;if (!ans) {recw=w;recc=c;ans=i;continue;}
		LL now=w*recc,rec=c*recw;if (now>rec) {recw=w;recc=c;ans=i;}
	}//直接踩中了这题的雷,直接除的话会爆精度 
	printf("%d\n",ans);
	return 0;
}

T2_tank:排序+贪心+二分

题目描述

Lemon最近迷上了一款坦克对战游戏。在这款游戏中,Lemon需要驾驶一辆坦克与敌军对战。

坦克有很多不同的武器,每种武器有各自的特点,而Lemon所要做的就是合适地发射这些武器,对敌军造成最大的伤害。具体来说,每个武器都有两个参数:攻击力D和攻击半径R。为了简化题意,我们保证所有武器的攻击力D均不相同,所有武器的攻击半径R也均不相同。

Lemon决定对这些武器的性能进行评价。当然,评价不能只看攻击力D,也不能只看攻击半径R。Lemon觉得一件武器A优于另一件武器B,当且仅当A的攻击力大于B的攻击力且A的攻击半径大于B的攻击半径。

接下来,Lemon想要对武器进行分组,Lemon按照以下方式分组:
首先,我们定义f(A,S)为真当且仅当在武器集合S中的任何武器都不比武器A性能更优秀。
(1)令i=0;
(2)令i=i+1;令S=还没被分组的武器集合;
(3)对于每一件S中的武器A,如果f(A,S)为真,则将武器A标记为第i组(注意S在这个过程中始终保持不变);
(4)如果所有武器均被分组则结束,否则转2。
给定N个武器的D和R,你的任务是按照Lemon的规则对这些武器进行分组。

输入输出格式

输入格式

输入文件第一行包含一个正整数N,表示武器的个数。

接下来N行,每行两个正整数D、R,描述一件武器的攻击力和攻击半径。保证所有的D两两不同,所有的R两两不同。

输出格式

输出文件包含N行,每行一个正整数,第i行的数表示第i件武器被分在了哪一组。

样例

INPUT

5
1 4
2 2
3 3
4 1
5 5

OUTPUT

2
3
2
2
1

HINT

首先我们发现武器5比武器1性能更优,所以武器1不在第1组。同理武器2、3、4也不在第1组。
但没有武器比武器5性能更优,因此武器5在第1组。
接下来还剩武器1、2、3、4没被分组。我们发现这些武器中没有武器比武器1更优,于是武器1在第2组,同理武器3、4也在第2组。
接下来只剩武器2了,武器2在第3组。
此时所有武器均被分组,过程结束。

对于20%的数据,N≤100;对于40%的数据,N≤3000;对于100%的数据,N≤100000;1≤R、D≤10^9。

SOLUTION

贪心

\(O(n^2)\)的做法好像不会,因为觉得写的很麻烦,索性放弃。其实写对贪心有一部分就是歪打正着qwq

把每个武器的两个参数\(d,r\),分别为第一关键字和第二关键字排序,得到了以d为主关键字降序的新序列。我们已知,对于第\(i,j\)个武器只有当①\(d_i>d_j\)且②\(r_i>r_j\)时编号为\(i\)的武器才能完全压制编号为\(j\)的武器,这时就不能把\(i,j\)分为一组。

所以当我们把所有武器以\(d\)为主关键字降序排序时已经保证了①条件的成立,所以若要使后面的武器\(i\)能够不被第\(j\)组完全压制,只有满足\(r_i>R_{j}\)时才能成立,若不能成立,我们就开一个新的组,这时候可以看出我们最后的\(R\)数组是满足单调性的,于是就可以二分。

于是我们的\(O(nlogn)\)的算法就出来了。

\(P.S.\)下面是我考场上的代码,其中\(lft\)数组是赘余的,而且变量命名也很奇怪,将就着看吧qwq。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
#define Min(a,b) ((a<b)?a:b)
#define Max(a,b) ((a>b)?a:b)
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-48;ch=getchar();}
	return x*f;}
const int N=101000;
int n,blg[N],lft[N],rgt[N],L,R,sch;
struct NODE{int d,r,u;}nd[N];
bool cmp(NODE x,NODE y){if (x.d==y.d) return x.r>y.r;return x.d>y.d;}
inline void bsh(int l,int r){
	if (l==r) {sch=l;return;}
	if (l+1==r) {if ((L>=lft[l])||(R>=rgt[l])) sch=l;else sch=r;return;}
	int mid=(l+r)>>1;
	if ((L>=lft[mid])||(R>=rgt[mid])) bsh(l,mid);else bsh(mid+1,r);}
int main(){
	freopen("tank.in","r",stdin);
	freopen("tank.out","w",stdout);
	int i,j;
	n=read();for (i=1;i<=n;++i) {nd[i].d=read();nd[i].r=read();nd[i].u=i;}
	sort(nd+1,nd+1+n,cmp);memset(blg,0,sizeof(blg));
	memset(lft,0,sizeof(lft));memset(rgt,0,sizeof(rgt));
	int cnt=0;
	for (i=1;i<=n;++i){
		L=nd[i].d,R=nd[i].r;
		//printf("*%d *%d:",L,R);
		bsh(1,cnt+1);if (sch==(cnt+1)) cnt++;
		lft[sch]=Max(lft[sch],L);rgt[sch]=Max(rgt[sch],R);
		blg[nd[i].u]=sch;
		//printf("cnt:%d sch:%d\n",cnt,sch);
	}
	for (i=1;i<=n;++i) printf("%d\n",blg[i]);
	return 0;
}

T3_catch:最短路树+树形dp

题目描述

Lemon因为偶然的原因,当上了警察局长。而一上任,他就碰到了个大麻烦:追捕周克华。
周克华是人尽皆知的抢劫杀人犯,而就在几天前,他在Lemon辖区内的银行门口,枪杀了一名储户后逃之夭夭。Lemon知道,如果他抓不住周克华,他的警察局长恐怕就当不下去了。为了能继续当他的警察局长,Lemon决定倾警察局之物力全力追捕。Lemon的辖区可以表示为一张边上带权的无向图。银行位于结点1。
Lemon仔细研究周克华的案底后得出以下结论:
首先,周克华拥有极强的反侦查能力,因此,他深知不走回头路的重要性。他永远不会访问任何一个结点两次。
其次,周克华深知多走一分钟路就多一分钟暴露的危险,而且他之前已经完全摸清了辖区的地形,因此他总是走最短路,也就是,他访问任何一个结点时,走的路线都是从银行到这里的最短路。为了简化题目,我们保证从银行(结点1)到任何一个结点的最短路都是唯一的。
再次,周克华知道,为了尽可能远离案发现场,他必须不停的运动。也就是说,只要有相邻的结点能满足“不走回头路、只走最短路”的前提,他一定会移动。如果有多个相邻结点可供选择,他会随机等概率选择一个作为他的移动目标。如果没有结点满足这一要求,那么周克华就会选择遁入深山之中,而可以想象在距离案发现场十万八千里的山区里抓捕周克华的难度,所以一旦周克华遁入山中,也就意味着Lemon的抓捕行动失败了。
Lemon分析出了以上结论后决定,只能在结点上布置警察,实施埋伏抓捕。但是,周克华的身体素质、反侦查能力和使用武器技术都十分优秀,因此,即使周克华遇到了埋伏,也有一定几率杀害所有参与埋伏的警察后逃脱。当然,随着埋伏的警察的数目的增多,逃脱几率会减小。如果逃脱成功,周克华会像什么都没发生一样,继续按上文所述的方式行动。
注意,周克华一旦到达一个结点,埋伏在那里的警察会立即实施抓捕,只有周克华逃脱了在当前结点的抓捕后才能进行下一步行动(遁入群山或继续移动),包括结点1,也就是周克华需要先逃脱结点1的埋伏才能走出他的第一步。
Lemon知道,他的设置警力方式决定了追捕成功的概率。他现在已经知道了他的辖区地图以及在不同地点设置不同数量的警力能成功抓捕周克华的概率,Lemon现在想要找到一个尽量优的方式设置警力,因此求助于你。你能告诉Lemon在最优的设置下,抓捕成功概率是多少吗?Lemon到时或许会把高额的悬赏分给你一部分的哦。

输入输出格式

输入格式

输入文件第一行包含两个数N,M,分别表示辖区里的结点数目和边的数目。
接下来M行,每行3个数a、b、c,表示结点a和b之间有一条权值为c的无向边。
接下来一个数S,表示可以参与埋伏的警察个数。
接下来N行,每行S个数,第i行第j个数Pij表示在结点i埋伏j个警察抓捕成功的概率。注意,如果不埋伏任何警察,那么显然绝不可能成功抓住周克华。

输出格式

输出文件仅包含一个实数,保留到4位小数,表示在最优警力设置下,抓捕成功的概率。

样例

INPUT

4 4
1 2 1
1 3 2
2 4 3
3 4 1
2
0.01 0.1
0.5 0.8
0.5 0.8
0.7 0.9

OUTPUT

0.6000

HINT

地图如下。(括号内的数为权值)

1 ---(1)--- 2
|           |
|           |
(2)        (3)
|           |
|           |
3 ---(1)--- 4

周克华在结点1会等概率选择2或3逃跑。如果选择了2,那么下一步他会选择遁入群山,因为1已经访问过了,而继续往4走就不是最短路了(1=>3=>4比1=>2=>4短);
如果选择了3,那么下一步他会继续往4跑,然后选择遁入群山(到达4后继续往2跑也不是最短路)。
最优警力设置是:在2、4处各设置一名警察。这样如果周克华在第一步选择了2(50%概率),那么在2处有50%概率被抓,如没有被成功抓住则遁入群山。如果第一步选择了3(50%概率),在3处被抓概率为0(因为没有警察埋伏),但接下来周克华会往4走(100%概率),在4处被抓的概率是70%所以总成功率是50%50%+50%70%=0.6。
数据范围 Data Range:
对于20%数据,满足N、S≤6;
对于50%数据,满足N、S≤30,每个结点度数不超过3;
对于100%数据,满足N、S≤200,M≤20000,1≤a、b≤N,1≤c≤10000,0<Pij≤1。
数据保证图中没有自环或重边,从结点1到任何一个结点的最短路唯一。

SOLUTION

最短路树+树形dp(树上背包)

其实这题看了题面就可以get它是让我们先求个以\(1\)为源点的单源最短路在跑个dp,然后又已知题目保证最短路的唯一性,且犯人并不会走回头路,我们就可以想到满足题意的路径构成为一棵最短路树。(考场上没想到“最短路树”这个词,想到的解法却是求最短路树的qwq)。

因为数据范围十分友好,是可以\(O(n^3)\)乱做爆搞的类型,所以果断选择Floyd做了非常简单的最短路处理。

最短路树的构建也十分简单,跟最短路图的构建差不多,在计算出点\(u,v\)\(1\)为起点的最短路长度\(dist[u],dist[v]\)之后,对于\(u,v\)之间边权为\(w\)的边\(u→v\),若\(dist[v]==dist[u]+w\)则可以判定边\(u→v\)在最短路树上,否则边\(u→v\)一定不在最短路上。

构建完最短路树,就开始考虑dp,显然地dp数组是要开两维的,\(dp[i][j]\)代表第\(i\)个节点布置\(j\)个警察的情况,然后因为是要把\(S\)个警察分配到不同节点(也可能只有一个),自然而然地想到了背包。

考场上我想到了做背包,因为懒得在dfs里滚背包,所以就在跑完一遍最短路树之后顺便通过取这个点的父节点的分叉数的倒数\(cntp\)乘以父节点被经过的概率,把从源点到每个点的概率算出来,最后直接在原有的概率\(p[i][j]\)基础上乘以我们所算的概率,再在dfs外面滚背包。然后就爆掉了,考场上我还把背包的循环套错了导致直接爆零,考后按照这个思路改掉循环也只能有30pts。

我的算概率的思路在我看来没有问题一开始旁边人也没有找到错误所在,但是,在拜托zzr帮我找错误的时候发现:如果这么算的话,我在某些测试点输出的答案可能会大于\(1\)

为什么会出这种问题?zzr一开始没有办法表述他的感觉,然后我为了证明这种情况的可能性,我画了一个这样的图:

可以看出在这棵有一点丑的树上,根据我的计算,除了根节点外,每一个点被访问的概率都是\(\frac 1 2\),然后当我们的节点足够多或者是我们警察抓获犯人的概率足够大的情况下,我们的最终答案是有可能超过\(1\)的,这显然是错误的。

那么为什么是错误的呢?因为在我们这么算的时候,可能会把父节点拦截成功的概率加上了子节点拦截成功的概率,这是不符合题意的,因为犯人只有一个,在父节点拦截成功后的子节点是不可能会出现犯人的,自然没有拦截成功一说。所以我们的子节点\(v\)被访问的概率其实是\(cntp*(1.0-p(放走))\),又因为我们在父节点\(u\)布置警力的情况不同概率不同,所以不能单纯地乘上\(p[u][k]\)\(k\)是做背包时用的位置指针)。倒不如说我们的答案构成有两种:

  1. 父节点\(u\)不放警察。我们在\(u\)把犯人放跑的概率为\(1\)
  2. 父节点\(u\)放了\(k\)名警察。我们在\(u\)把犯人放跑的概率为\((1-p[u][k])\),所以这样可以得到:我们的所有子节点被访问的概率此时应该都是\((1-p[u][k])*cntp\)。至于子节点以及子节点的子节点的警力的分配,直接背包做就行了,与前面的概率无关。最后乘上那个概率就行了。

所以我们就可以知道,只有在父节点\(u\)布置警力的情况下,我们才要把子节点的答案在转移时乘上访问概率。所以我们可以先按情况1做一遍背包,再在父节点\(u\)布置警力个数\(k\)确定(枚举)的情况下,计算情况2能否更新。

情况1的转移就是很普通的背包(当然要乘上基本的\(cntp\),因为不管是哪个情况都要乘)
情况2的转移方程就是\(dp[u][j+k]=Max(dp[u][j+k],dp[v][j]*(1-p[u][k])+p[u][k])\)

最后提醒一句:因为显然地,同一个节点不能多次参与答案的构成,所以做背包的时候要另开一个数组\(rec\)防止以上情况的发生(相当于\(rec\)记录了\(dp\)在被节点\(i\)更新前的答案)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
#define Min(a,b) ((a<b)?a:b)
#define Max(a,b) ((a>b)?a:b)
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-48;ch=getchar();}
	return x*f;}
const int N=210;
const int INF=200001000;
int n,m,S,sq[N][N],dist[N][N];
double dp[N][N],p[N][N],w[N],rec[N];
void dfs(int u){
	int cnt=0,q[N];
	for (int i=1;i<=n;++i){
		if (!sq[u][i]) continue;
		if ((dist[1][u]+sq[u][i])==dist[1][i]) {q[++cnt]=i;dfs(i);}
	}
	if (!cnt) {for (int i=1;i<=S;++i) {dp[u][i]=p[u][i];}return;}
	double cntp=(double)1/cnt;
	for (int i=1;i<=cnt;++i){
		int v=q[i];
		//printf("%d:%d\n",u,v);
		for (int j=0;j<=S;++j) rec[j]=dp[u][j];
		for (int j=S;j>=1;--j){
			for (int k=(S-j);k>=0;--k){
				if (rec[k]>=0) dp[u][j+k]=Max(dp[u][j+k],
					rec[k]+cntp*dp[v][j]);
			}
		}
	}
	for (int j=0;j<=S;++j) rec[j]=dp[u][j];
	for (int j=1;j<=S;++j){
		for (int k=(S-j);k>=0;--k){
			if (rec[k]>=0) dp[u][j+k]=Max(dp[u][j+k],(1.0-p[u][j])*rec[k]+p[u][j]);
		}
	}
}
int main(){
	//freopen("catch.in","r",stdin);
	//freopen("catch.out","w",stdout);
	int i,j;
	memset(sq,0,sizeof(sq));
	n=read();m=read();
	for (i=1;i<=n;++i) for (j=1;j<=n;++j) dist[i][j]=INF;
	for (i=1;i<=m;++i){int u=read(),v=read(),w=read();
		sq[u][v]=w;sq[v][u]=w;dist[u][v]=w;dist[v][u]=w;}
	S=read();
	for (i=1;i<=n;++i) {for (j=1;j<=S;++j) dp[i][j]=-1;dp[i][0]=0;}
	for (i=1;i<=n;++i) for (j=1;j<=S;++j) scanf("%lf",&p[i][j]);
	for (i=1;i<=n;++i) {p[i][0]=0;dist[i][i]=0;}
	for (int k=1;k<=n;++k)
		for (i=1;i<=n;++i)
			for (j=1;j<=n;++j){
				if ((dist[i][k]!=INF)&&(dist[k][j]!=INF))
					dist[i][j]=Min(dist[i][j],dist[i][k]+dist[k][j]);
			}
	dfs(1);
	double ans=0;
	//for (i=1;i<=S;++i) ans=Max(ans,dp[i]);
	//for (i=1;i<=n;++i) {for (j=0;j<=S;++j) printf("*%0.4lf ",dp[i][j]);puts("");}
	for (i=1;i<=S;++i) ans=Max(ans,dp[1][S]);printf("%0.4lf",ans);
	return 0;
}
posted @ 2018-10-22 10:55  O-GUYA  阅读(242)  评论(1编辑  收藏  举报
//接下来是那个华丽的鼠标点击效果代码!