「20181024模拟」Solution
Posted forwardfuture
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「20181024模拟」Solution相关的知识,希望对你有一定的参考价值。
先把题目讲讲,教训什么的放到讲完再说
Problem 1. 密谋
题目描述
丁国建造了 n 座激光塔。
“ 当若干座激光塔相连并形成环形线路,你就能获得上神的力量。”这是丁国国王从大魔王那得知的消息。
第二天,丁国国王便下令修建 (m) 条连接激光塔的线路。
你作为甲国派来的间谍,无意间听到了这个消息,当即决定破坏这场密谋。每座激光塔的坐标为((x_i,y_i)),正在修建的线路((u_i,v_i))表示连接编号为 (u_i) 和 (v_i) 的激光塔,破坏该条线路的代价为两座激光塔的直线距离。求破坏这场密谋的最小代价。
输入格式
第一行两个整数 (n, m)
接下来 (n) 行, 每行两个整数 (x_i), (y_i), 代表点 (i) 的坐标
接下来 (m) 行, 每行两个整数 (u_i), (v_i), 代表一条边 ((u_i, v_i))
输出格式
输出一个实数代表答案, 当答案与标准答案之差的绝对值不超过 (10^{-4}) 时视为正确。
样例数据
input1
3 3
0 0
0 1
1 0
1 2
2 3
1 3
output1
1.000000000
input2
8 8
0 0
3 0
3 3
0 3
1 1
1 2
2 2
2 1
1 2
2 3
3 4
4 1
5 6
6 7
7 8
8 5
output2
4.000000000
数据规模与约定
对于前 (20\%)的数据:(n,m leq 20)
对于前 (50\%)的数据:(n,m leq 1 000)
对于 (100\%) 的数据:(1 leq n leq 10000) , (1 leq m leq 50000) , (|x_i|,|y_i| leq 10000) , (u_i ! = v_i),不存在两点重合的情况.
时间限制:1s
空间限制:512MB
题意很简单,在平面直角坐标系中给出 (n) 个点,并给出连接这 (n) 个点的 (m) 条边,边的权值为这两个点的欧拉距离,断掉某条边的代价即为这条边的权值。要求花费最小的代价使得剩下的边无法构成环形线路
很明确的一点,每个包含 (k) 个点的联通块必由 (k-1) 条边连接起来,其余的边必须被断掉。因为在 (k-1) 条边的基础上,若再多出来一条边,那么这条边必定会使得两点之间的路径不唯一,也就是产生了环形线路,不符题意。在此基础上,我们要使得断边的代价最小,也就是使留下的边权和最大,那么直接跑一遍最大生成树,然后将未加入最大生成树的边权和输出即可。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e4+10;
const int M=5e4+10;
int n,m,x[N],y[N],b[N];
double ans;
struct edge{int a,b;double c;}ed[M];
inline double dis(int a,int b){return sqrt(((double)x[a]-x[b])*((double)x[a]-x[b])+((double)y[a]-y[b])*((double)y[a]-y[b]));}
bool cmp(edge x,edge y){return x.c>y.c;}
inline int f(int x){if(b[x]!=x)b[x]=f(b[x]);return b[x];}
int main(){
freopen("plan.in","r",stdin);freopen("plan.out","w",stdout);
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]);
for(register int i=1;i<=m;i++)scanf("%d%d",&ed[i].a,&ed[i].b),ed[i].c=dis(ed[i].a,ed[i].b);
sort(ed+1,ed+m+1,cmp);for(register int i=1;i<=n;i++)b[i]=i;
for(register int i=1;i<=m;i++){
int f1=f(ed[i].a),f2=f(ed[i].b);if(f1==f2){ans+=ed[i].c;continue;}b[f2]=f1;}
printf("%.9lf
",ans);
fclose(stdin);fclose(stdout);
return 0;
}
Problem 2. 对决
题目描述
丁国国王的密谋被破坏了。
大魔王很生气,并且他知道是你坏了他的好事,于是他找来你。
“跟我玩个游戏,你赢了,我就放你走。”
游戏是这样的:你和大魔王在一个规模为 (1 * n) 的棋盘中下棋,第一个人可以下在第(1) 到 (m) 中的任意一个位置。接下来每一个人可以下在第 (i + 1) 到 (i + m) 的任意一个位置,其中 (i) 为上一个人下棋的位置。每个格子里有一个数,如果一个人下棋在格子 (i),会得到 (a[i])的分值。当不能继续操作时,游戏结束。
大魔王允许你编个程序计算一下,当你和大魔王都采取 最优策略时, 你的得分减去大魔王的得分。
你们决定通过掷硬币来决定先后手,因此你要把你先后手的两种情况都考虑一下。
输入格式
第一行:两个正整数 (n) 和 (m) , 用空格隔开。
第二行:(n) 个数,表示棋盘上的数字。
输出格式
两行,每行各一个数, 第一个数为你先手时的答案, 第二个数为你后手时的答案。
样例数据
input1
1 3
1
output1
1
-1
input2
2 100
2 2
output2
2
-2
数据规模与约定
对于前 (30\%)的数据:(n leq 15)
对于前 (60\%)的数据:(m leq 100)
对于 (100\%) 的数据:(1 leq n,m leq 100000) ,a 数组中的数保证在 int 范围内。
时间限制:1s
空间限制:512MB
当时在考场里看到这道题是懵逼的:What?这不是九省联考一双木棋?这么难的博弈论题我还能做?于是胡乱搞一阵,如愿以偿的拿到了10分。好吧,其实冷静分析一下也不算是太难:
首先我们明确一下,这题是动归题,那么显然可以设 (dp[i][0/1]),当第二维为 (0) 时,表示我在选,反之则表示大魔王在选,(i) 表示正在选的区间为 ([i,min(i+m-1,n)])。其次,要注意的是此题不能正序DP,因为当前点的最优决策一定是由后续状态的决策转移而来,类比于绝大多数概率期望DP,如果正序DP则无法知晓其后续状态的决策了,所以选择倒序DP
那么有DP方程:
[dp[i][0]=max{dp[j+1][1]+a[j]|ileq jleq min(i+m-1,n)}]
与
[dp[i][1]=min{dp[j+1][0]-a[j]|ileq jleq min(i+m-1,n)}]
其实还有另一种更方便的设法,只是我一时没反应过来没想到:把之前的设法中的第二维删去,那么就有新的DP方程:
[dp[i]=max{-dp[j+1]+a[j]|ileq jleq min(i+m-1,n)}]
假设上家为A,当前这家为B,那么 (dp[j+1]) 是A相对于B的差值,当我们开始处理B时,就要对 (dp[j+1]) 取个负号表示B相对于A的差值,由于我们需要最大化B相对于A的差值,所以对该式取max即可
但由于这样转移的复杂度是 (O(nm)) 的,所以使用单调队列优化掉 (m),将复杂度降为线性 (O(n)),通过本题
代码是上面一种较烦的方法
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int INF=1e9;
int n,m;
ll a[N],dp[N][2],q[N],p[N],head,tail,h,t;
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++)scanf("%lld",&a[i]);
q[head=tail=1]=n;p[h=t=1]=n;
for(register int i=n;i>=1;i--){
while(head<=tail&&dp[q[tail]+1][1]+a[q[tail]]<=dp[i+1][1]+a[i])tail--;
q[++tail]=i;
while(h<=t&&dp[p[t]+1][0]-a[p[t]]>=dp[i+1][0]-a[i])t--;
p[++t]=i;
while(head<=tail&&q[head]>=i+m)head++;
dp[i][0]=dp[q[head]+1][1]+a[q[head]];
while(h<=t&&p[h]>=i+m)h++;
dp[i][1]=dp[p[h]+1][0]-a[p[h]];
}
printf("%lld
%lld
",dp[1][0],dp[1][1]);
fclose(stdin);fclose(stdout);
return 0;
}
Problem 3. 寻路
题目描述
至于丁国国王的下场……
大魔王一气之下把他扔进了迷雾森林。迷雾森林可以看作一棵有 n 个节点的树,根节点为 (1),但由于“Birds are everywhere.”,他得到了种种不一样的消息,每份消息中都会告诉他有两棵子树是禁忌之地,于是他向你求助了。
他给出了 (q) 个形如x y
的询问,表示他不能走到 (x) 和 (y) 的子树中,由于走的路径越长他找到出口的概率越大并且他 只能走一条不经过重复节点的路径,现在他想知道对于每组询问他能走的最长路径是多少,如果没有,输出零。
你刚刚在对决中打败了大魔王,心情愉悦,决定帮丁国国王一把。
当然,出于本国利益,你也可以不帮他。
输入格式
第一行两个正整数 (n) 和 (q)((1 leq n,q leq 100000))
第二到第 n 行每行两个整数 (u,v) 表示 (u) 和 (v) 之间有一条边连接,边的长度为 (1)。
接下来 (q) 行每行两个 (x,y) 表示一组询问,意义如题目描述。
输出格式
q 行,输出见题目描述
样例数据
input
5 2
1 3
3 2
3 4
2 5
2 4
5 4
outpu
1
2
询问 (1) 中 (2) 和 (4) 的子树不能走,最长路径为((1,3))长度为 (1)
询问 (2) 中 (5) 和 (4) 的子树不能走,最长路径为((1,3,2))长度为 (2)
数据规模与约定
对于前 (20\%)的数据:(1 leq n,q leq 100)
对于前 (50\%)的数据:(1 leq n,q leq 2000)
对于 (100\%) 的数据:(1 leq n leq 100000) , (1 leq m leq 50000)
时间限制:1s
空间限制:512MB
做这题前,得先了解一个关于树的直径的性质:即将一棵树切成两部分,那么原来那棵树的直径一定在这两部分的直径的四个端点之间
有了这个性质,我们便可以把原树中任意两棵树的直径合并起来得到一个更大的直径。因此,直接用线段树维护DFS序某区间的直径两端点,查询时合并一下即可
关于查询LCA,最好用欧拉序的方法实现 (O(1)) 查询,因为本题merge操作常数较大,直接倍增LCA (O(logn)) 可能会被卡常
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,q,U,V,head[N],cnt,pre[N],las[N],p[N],dep[N<<1],depth[N],anc[N<<1][20],vk[N<<1],cl,cll,num[N],x,y;
struct edge{int nxt,to;}ed[N<<1];
struct data{int x,y;}seg[N<<2],ans;
inline int read(){
int h=0;char ch=‘ ‘;while(ch<‘0‘||ch>‘9‘)ch=getchar();
while(ch>=‘0‘&&ch<=‘9‘)h=h*10+ch-‘0‘,ch=getchar();return h;}
inline void addedge(int x,int y){
ed[++cnt].to=y;ed[cnt].nxt=head[x];head[x]=cnt;
ed[++cnt].to=x;ed[cnt].nxt=head[y];head[y]=cnt;}
inline void DFS(int u,int fa,int de){
dep[++cll]=de;vk[cll]=u;p[u]=cll;pre[u]=++cl;num[cl]=u;depth[u]=de;
for(register int i=head[u];i;i=ed[i].nxt){
int v=ed[i].to;if(v==fa)continue;
DFS(v,u,de+1);dep[++cll]=de;vk[cll]=u;p[u]=cll;}
las[u]=cl;}
inline void Preprocess(){
for(register int i=1;i<=cll;i++)anc[i][0]=i;
for(register int j=1;(1<<j)<=cll;j++)
for(register int i=1;i+(1<<j)-1<=cll;i++){
int a=anc[i][j-1],b=anc[i+(1<<(j-1))][j-1];
anc[i][j]=dep[a]<=dep[b]? a:b;}
}
inline int LCA(int x,int y){
int L=p[x],R=p[y],LOG=0;if(L>R)swap(L,R);
while(1<<(LOG+1)<R-L+1)LOG++;
int a=anc[L][LOG],b=anc[R-(1<<LOG)+1][LOG];
return dep[a]<=dep[b]? vk[a]:vk[b];}
inline int dis(int x,int y){return depth[x]+depth[y]-(depth[LCA(x,y)]<<1);}
inline data merge(data a,data b){
if(!a.x)return b;if(!b.x)return a;
int val=dis(a.x,a.y),w;data c=a;
if((w=dis(a.x,b.x))>val)val=w,c.x=a.x,c.y=b.x;
if((w=dis(a.x,b.y))>val)val=w,c.x=a.x,c.y=b.y;
if((w=dis(a.y,b.x))>val)val=w,c.x=a.y,c.y=b.x;
if((w=dis(a.y,b.y))>val)val=w,c.x=a.y,c.y=b.y;
if((w=dis(b.x,b.y))>val)val=w,c.x=b.x,c.y=b.y;
return c;}
inline void build(int o,int L,int R){
if(L==R){seg[o]=(data){num[L],num[L]};return;}
int M=(L+R)>>1,lc=o<<1,rc=(o<<1)+1;
build(lc,L,M);build(rc,M+1,R);
seg[o]=merge(seg[lc],seg[rc]);}
inline data query(int o,int L,int R,int l,int r){
if(l<=L&&R<=r)return seg[o];
int M=(L+R)>>1,lc=o<<1,rc=(o<<1)+1;data c=(data){0,0};
if(l<=M)c=merge(c,query(lc,L,M,l,r));if(r>M)c=merge(c,query(rc,M+1,R,l,r));
return c;}
int main(){
freopen("find.in","r",stdin);freopen("find.out","w",stdout);
n=read();q=read();
for(register int i=1;i<n;i++)
U=read(),V=read(),addedge(U,V);
DFS(1,0,1);Preprocess();build(1,1,n);
while(q--){
x=read();y=read();ans=(data){0,0};
if(x==1||y==1){printf("0
");continue;}
int l1=pre[x],r1=las[x],l2=pre[y],r2=las[y];
if(l1>l2)swap(l1,l2),swap(r1,r2);
if(l1<=l2&&r2<=r1){
if(1<=l1-1)ans=merge(ans,query(1,1,n,1,l1-1));
if(r1+1<=n)ans=merge(ans,query(1,1,n,r1+1,n));}
else{
if(1<=l1-1)ans=merge(ans,query(1,1,n,1,l1-1));
if(r1+1<=l2-1)ans=merge(ans,query(1,1,n,r1+1,l2-1));
if(r2+1<=n)ans=merge(ans,query(1,1,n,r2+1,n));}
printf("%d
",dis(ans.x,ans.y));
}
fclose(stdin);fclose(stdout);
return 0;
}
总结
本次考试是目前6次模拟赛中考的最差的一次,第一题数组没开够,第二题以为是博弈论不敢想太多,于是便以 (120) 分的好成绩掉到了机房 (rank6),被一群神仙教育。。。所以,以后需要注意的是:
1.数组要记得检查是否开足
2.有些类似博弈论的“最优策略”题动态规划有时候也同样可做
3.注意分析题目性质
4.考场策略和状态很重要,记得及时调整
以上是关于「20181024模拟」Solution的主要内容,如果未能解决你的问题,请参考以下文章
css 这是来自http://hackingui.com/front-end/a-pure-css-solution-for-multiline-text-truncation/的片段