在这个非常特殊的日子来了一次虐狗的考试。(奸笑 )
这次测试不是考的很好,因为没有考过qt啊啊啊!!!!
考试历程:
上次考试一下跌入谷底,是因为每一道题都兼顾导致每一道题都没能深刻思考,分数不堪入目。于是我下定决心要扳回一城。
第一题的DP状态设置的时候设了好多种情况,很多种写在程序上时写着写着就转移不下去了,或者说有什么后效性吧,最后好不容易才花了1小时多才really推出来,能写出我就很开心了嘿嘿,不过耗时还是挺长,还是要多练。
第二题就是个版子我不多说。
第三题咋一看还以为什么高超的dp,然后我又写不出,于是只能快速写了个dfs外加一大堆优化然鹅并没有什么卵子用(╥╯^╰╥),完美TLE。【本来应该是有30分的暴力分,后来我发现一个坑,当输入同布流和斯堪福scanf printf连用时可能会产生一些奇奇怪怪的东西,于是我决定以后再也不用同布流了(╬ ̄皿 ̄)=○】
第四题一看见最大的最短立马想到二分答案+spfa(),考试中想到sm大佬讲过类似的二分思想,于是check函数一会便推出,然后本以为皆大欢喜可以A了,MM的正解居然是最小生成树?!excuse me???
不多说,还是练的太少,掌握不到位,唉。
一.马拉松
在二维平面上有N个点,从(x1,y1)到(x2,y2)的代价为|x1-x2|+|y1-y2|。
求从1号点出发,按从1到N的顺序依次到达每个点的最小总代价。
你有K次机会可以跳过某个点,不允许跳过1号点或N号点。
【题解】:类似于导弹拦截的状态设置,如果不加一些状态的限制便很难转移。
我们设 f[i][j] 表示目前位置为第i个点,前面一定跳过了j个点的最小代价(j<=i-2,第一个点和当前点不能跳)
那么很明显i 号点是由前 i-1号点拓展过来的,那么转移便很容易了。
code
//1
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct fuc
{
int x,y;
}a[510];
int n,k,f[501][501]; //目前所在位置为i,前面已经跳过了j个,的最小值
int main()
{
freopen("marathon.in","r",stdin);
freopen("marathon.out","w",stdout);
scanf("%d%d",&n,&k);
memset(f,10,sizeof(f));
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&a[i].x,&a[i].y);
}
f[1][0]=0;
for(int i=2;i<=n;i++)
for(int j=0;j<=min(i-2,k);j++)
for(int z=1;z<=i-1;z++)
if(j>=i-z-1) f[i][j]=min(f[i][j],f[z][j-i+z+1]+abs(a[i].x-a[z].x)+abs(a[i].y-a[z].y));
//i-z-1表示z--i中间的数的个数,从z跳到i中间跳过的数量
printf("%d",f[n][k]);
return 0;
}
二.Sta
题目大意:给出一个N个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大。
【题解】:板子题,树上一点到其他点的距离和最大,不会的可以见我的博客不会请点我的树形dp系列。
code
//2
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll top=0,first[1000001],n,size[1000001],f[1000001],g[1000001],maxx=-100,ans=0;
struct fuc
{
int x;
int next;
}a[2001000];
void add(int x,int y)
{
top++;
a[top].x=y;
a[top].next=first[x];
first[x]=top;
}
void dfs(int fa,int x)
{
size[x]=1;
for(int i=first[x];i;i=a[i].next)
{
if(a[i].x==fa) continue;
dfs(x,a[i].x);
size[x]+=size[a[i].x];
f[x]+=f[a[i].x]+size[a[i].x];
}
}
void dfsrfy(int fa,int x)
{
if(x!=1) g[x]=g[fa]+(n-size[x]-size[x]);
for(int i=first[x];i;i=a[i].next)
{
if(a[i].x==fa) continue;
dfsrfy(x,a[i].x);
}
}
int main()
{ freopen("sta.in","r",stdin);
freopen("sta.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n-1;i++)
{
int xx,yy;
scanf("%d%d",&xx,&yy);
add(xx,yy);
add(yy,xx);
}
dfs(0,1);
g[1]=f[1];
dfsrfy(0,1);
for(int i=1;i<=n;i++)
{
if(g[i]>maxx)
{
maxx=g[i];
ans=i;
}
}
printf("%d\n",ans);
}
三.穿越
由于某OJ尚未公布此题(却拿来做考试题 ),所以我只能自己搬题面了(;´д`)ゞ、
【问题描述】
小x听说某大国很流行穿越,于是他就想写一个关于穿越的剧本。
闲话休提。话说小x穿越到了某一个剑与魔法的大陆。因为如此这般,所以小x从维娜艾那里得到了预言。小x一共被告知了若干件按顺序结算的事件。这些事件分为两类:战役事件(CASE)、穿越回去事件(END)。
战役事件可以选择是否参加,参加了之后会获得一个RP和一定的金钱。
每个END事件发生需要至少参加一定数量的战役事件(足够的RP值)。特别的是,END事件如果满足要求(当下小x已经有了足够RP值),就会强制(必须)发生。
小x希望在大陆玩个够,所以他要求只有最后一个END事件会发生,且小x希望满足上述情况下,能够获得最多的金钱,所以求助于你。
【输入】
第一行一个数N,表示输入文件有多少行。
接下来每一行用空格隔开一个字符和一个整数。
字符为“c”表示战役事件,接下来的整数表示这次涨RP顺带有多少钱;
字符为“e”表示穿越回去事件,接下来的整数代表至少要涨多少RP。
最后一个事件保证是END事件。
【输出】
第一行一个整数,最多金钱数目。
若不可能则输出-1。
【数据范围】
30%的数据满足 N<=20
60%的数据满足 N<=1,000
100%的数据满足 N<=200,000
每次涨RP事件赏金不超过10,000
穿越事件的要求不超过200,000
这种数量级范围我用dfs如果不用输入同步流的话是可以拿30分,尽管有很多没什么用的优化。
【题解】:yzx大佬考场中写出了正解却头文件打错爆0(滑稽 ),告诉我们什么,想出了正解也不要扬扬得意,没准你一时眼瞎文件名打错了呢对不(๑╹◡╹)ノ"""
不过我是真没想到这居然是一个贪心。
明显,碰到一个end事件那么我们就要从前面选择不超过此rp值个事件使价值最大,那么我们就先把价值最小的扔掉不要,用一个小根堆来维护这样的属性。碰到c事件便push进去,碰到一个end事件就把pop是的小根堆里的元素数<rp 【由于最后一个end它没有限制,这时我们不用pop了,特判一下便可】
code
#include<bits/stdc++.h>
using namespace std;
priority_queue< int,vector<int>,greater<int> >q;
int n;
long long sum=0;
struct fuc
{
int x,v;
}a[200001];
int main()
{ freopen("dragons.in","r",stdin);
freopen("dragons.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{ char s;
int v;
cin>>s>>v;
if(s=='c') a[i].x=1;
a[i].v=v;
}
int tot=1;
for(int i=1;i<=n;i++)
{
if(a[i].x==1) q.push(a[i].v);
else
if(i!=n)
{
while(q.size()>=a[i].v)
{
q.pop();
}
}
if(i==n&&q.size()<a[i].v) //最终可能还回不了家(虽然某OJ没此类数据...)
{
printf("-1\n");
return 0;
}
}
while(!q.empty())
{
sum+=q.top();
q.pop();
}
printf("%lld",sum);
}
这件事还启示我们一个东西:要用脚能打出小根堆的巴拉巴拉的玩意儿。不然考试即使知道正解不是爆零就是 不会写。
四.最小瓶颈路
某OJ上又没有!!!
【问题描述】
给定一个包含n个节点和 m 条边的图,每条边有一个权值。
你的任务是回答k个询问,每个询问包含两个正整数 s和 t 表示起点和终点,要求寻找从s到t的一条路径,使得路径上权值最大的一条边权值最小。
【输入格式】
第一行包含三个整数n 、m、k,分别表示 n个节点, m 条路径,k 个询问。
接下来 m行,每行三个整数u,v,w, 表示一个由 u 到v 的长度为w 的双向边。
再接下来 k 行,每行两个整数 s,t,表示询问从 s连接到t 的所有路径中单边长度最大值的最小值。
【数据范围与提示】
对于30% 的数据 n<=100,m<=1000,k<=100,w<=1000
对于 70% 的数据 n<=1000,m<=10000,k<=1000,w<=100000
对于100% 的数据 n<=1000,m<=100000,k<=1000,w<=10000000
本题可能会有重边。
为了避免 Special Judge,本题所有的w 均不相同。
最大的最小,不是二分答案么???
完美TLE o(▼皿▼メ;)o
谁能想到正解是最小生成树%%%(wc有毒)
Kruskal算法原理便是把图中所有的边按从小到大排序,依次枚举,如果当前边的两端点已经连通便不操作,否则利用并查集将两点所在的连通块连在一起。
再来仔细看看我们的问题,如何计算两点之间最小瓶颈路上最大边的长度…
首先明确无向图中最小瓶颈路一定是在最小生成树中,除非本来就不连通。
我们知道Kruskal算法是按照边由小到大的顺序将点连接起来的,我们要求这两个点组成的图中最大的边最小,那么也就是只要两个点在一棵树中了,那么最初将他们所在连通量连接的边就是我们要求的距离了。十分的巧妙,仔细想想。
所以只需要在建树过程中离线的查询k组疑问,判断此时他们是否连在一起了,存储答案便OK。
code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int fa[1001],n,m,k,vis[1001],ans[1001]={},num;
struct fuc
{
int x,y,v;
}a[100010];
struct fuk
{
int x,y;
}b[1001];
bool mycmp(fuc a,fuc b)
{
return a.v<b.v;
}
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main()
{
freopen("package.in","r",stdin);
freopen("package.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int x,y,v;
scanf("%d%d%d",&x,&y,&v);
a[i].x=x,a[i].y=y,a[i].v=v;
}
sort(a+1,a+m+1,mycmp);
for(int i=1;i<=k;i++)
{
int x,y;
scanf("%d%d",&x,&y);
b[i].x=x,b[i].y=y;
}
num=n;//当前连通量数目
for(int i=1;i<=m;i++)
{
int u=find(a[i].x),v=find(a[i].y);
if(u!=v)
{
fa[u]=v;
num--;
for(int j=1;j<=k;j++)
if((!ans[j])&&(find(b[j].x)==find(b[j].y)))
ans[j]=a[i].v;
}
if(num==1) break;
}
for(int i=1;i<=k;i++)
{
if(!ans[i]) printf("-1\n");
else printf("%d\n",ans[i]);
}
return 0;
}
妙啊
考试能暴露出近期学习的不足,我们不能以一次考试的失误而失去信心,而应该去反思最近的学习是否积极思考,认真听讲,及时复习,用考试这一测试手段来进一步提升自我。