T1 三角
【题目描述】
ZGY 有一个三角,就像下面这样(每一个点都有一个权值)
第 1 层有 1 个,第 2 层有 2 个,第 i 层有 i 个。
这个三角一共有 n 层,ZGY 每次可以从第 i 层的第 j 个走到第 i + 1 层的第 j 个或是第 j + 1 个,直到走到第 n 层。从第 1 层走到第 n 层的一种方案成为一条路径,路径的权值为路径 上点权值之和。
现在 ZGY 想知道,权值前 k 大的路径(存在多个正确答案)。
【输入格式】
第一行,两个整数 n, k 表示 三角一共有 n 层,ZGY 想知道权值前 k 大的路径。
接下来 n 行: 其中第 i 行包含 i 个整数,其中第 j 个整数 Wij表示 第 i 层第 j 个点权值为 Wij
【输出格式】
输出数据包含 k 行,每行表示一条路径包含一个由“L” 和 “R”组成的字符串,长 度为 n - 1 其中第 i 个字符表示在第 i 层时向下一层走的方向。
假设当前在第 i 行第 j 个点,如果为“L”则走向第 i + 1 行第 j 个点,如果为“R”则走向第 i + 1 行第 j + 1 个点.
解析
因为我们可以确定权值总和最大的方案,所以可以考虑二分答案。
先预处理,从下往上dp出最大的方案。二分答案,二分一个mid,深搜。因为直接深搜的复杂度太高,所以要剪枝。若在当前节点的权值加上从当前节点到底最大的值仍小于mid,直接返回。如果统计到底了,记录方案数。若方案数大于等于k,l=mid+1;反之r=mid。
最后由答案再dfs一遍,输出答案即可。
细节
注意二分答案的细节,我被虐吐了。
代码
#include<cstdio>
#include<algorithm>
#define maxn 1005
using namespace std;
int n,k,sum,cnt,fl,nw;
int val[maxn][maxn];
int ans[maxn],dp[maxn][maxn];
void dfs(int x,int y,int sum,int lim)
{
if(nw>=k)
{
fl=1;
return;
}
if(sum+dp[x][y]-val[x][y]<lim)return;
if(x==n)
{
nw++;
return;
}
dfs(x+1,y,sum+val[x+1][y],lim);
if(fl)return;
dfs(x+1,y+1,sum+val[x+1][y+1],lim);
if(fl)return;
}//判断有无解
int check(int x)
{
fl=0;
nw=0;
dfs(1,1,val[1][1],x);
if(fl)return 1;
return 0;
}
void solve(int x,int y,int sum,int lim)
{
if(sum+dp[x][y]-val[x][y]<lim)return;
if(x==n)
{
for(int i=1;i<n;++i)
{
if(ans[i]==0)printf("L");
else printf("R");
}
printf("\n");
nw++;
return;
}
ans[x]=0;
solve(x+1,y,sum+val[x+1][y],lim);
if(nw==k)return;
ans[x]=1;
solve(x+1,y+1,sum+val[x+1][y+1],lim);
if(nw==k)return;
}
int main()
{
freopen("tri.in","r",stdin);
freopen("tri.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=i;++j)
{
scanf("%d",&val[i][j]);
sum+=val[i][j];
}
}
for(int i=1;i<=n;++i)
dp[n][i]=val[n][i];
for(int i=n-1;i>=1;--i)
for(int j=1;j<=i;++j)
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+val[i][j];
int l=0,r=dp[1][1],mid;
while(l<r)
{
mid=(l+r)>>1;
if(check(mid))l=mid+1;
else
r=mid;
}
nw=0;
solve(1,1,val[1][1],mid);
return 0;
}
总结
别忘了基础算法 Q A Q QAQ QAQ。
T2 C++锦标赛
【题目描述】
ZGY 报名参加了 C++锦标赛,在这个比赛中前 K 名可以获得一本《C++ Primer》,当然 ZGY 也想要一本。
这个比赛已经有 n 个人参加,并且互相之间已经进行了比赛,每一个人都有一个得分, 用于最后排名。
ZGY 作为第 n + 1 个参赛者,需要与之前的每一个人打一场比赛,当然 ZGY 最开始的得分等于 0。
对于每场比赛,有两个人参加,不会存在平局,胜者得分增加 1,败者得分不变。最后 按照得分从高到低来排名,假设有人与ZGY最终得分相同,那么如果那个人曾经输给了ZGY 就会排在 ZGY 后面,否则会排在 ZGY 的前面。
如果 ZGY 赢了某个人,就需要消耗相应的 RP 值,现在 ZGY 可以决定赢那些人,所以 ZGY 想知道最少需要消耗多少 RP 值才能够获得一本《C++ Primer》。
【输入格式】
本题包含多组数据,第一行读入一个整数 T 表示数据组数
对于每一组数据 第一行,包含两个整数 n, k 表示在 ZGY 之前已经有 n 个人参加比赛,前 k 名可以 获得《C++ Primer》。
接下来 n 行,每行包含两个整数 pi和 ei表示第 i 个人已经获得的得分和赢第 i 个人 所需要花费的 RP(注意在 ZGY 来之前这 n 个人的比赛得分不一定满足上述计分规则)
【输出格式】
对于每一组数据,输出一个整数,表示最少需要花费的 RP,如果无论如何也无法进入 前 K 名,则输出-1
解析
题目当中有这句话应当引起重视:
假设有人与ZGY最终得分相同,那么如果那个人曾经输给了ZGY 就会排在 ZGY 后面,否则会排在 ZGY 的前面
说明我们在做题时一定要考虑最后与ZGY得分相同的人。
还有一个细节:
胜者得分增加 1,败者得分不变
也就是说,当对手赢了之后,对手的得分也要加一。
说明我们要分类讨论(我也不知道这是怎么想出来的)。先把对手按照得分从大到小排序,确定ZGY的最小得分,设为 a i m aim aim,以及ZGY至少要超过多少个与最小得分相同的人数后才能刚好到第k名。
下面开始分类讨论:
case1
当ZGY的得分为
a
i
m
+
2
aim+2
aim+2时。因为若ZGY输给了所有得分为
a
i
m
aim
aim的人,他的得分依旧在第k名内。所以直接从小到大枚举
a
i
m
+
2
aim+2
aim+2个
r
p
rp
rp值,加起来即可。
ll work1()//得分为aim+2分,无忧无虑
{
ll ret=0;
for(ll i=1;i<=aim+2&&i<=n;++i)
ret+=tr[i].e;
return ret;
}
case2
当ZGY的得分为
a
i
m
+
1
aim+1
aim+1时,这是若ZGY输给了得分为
a
i
m
aim
aim或
a
i
m
+
1
aim+1
aim+1的人,他的地位可能不保,因为
a
i
m
aim
aim的得分会变成
a
i
m
+
1
aim+1
aim+1。假设有
a
a
a个人的得分大于等于
a
i
m
aim
aim,设
m
o
r
e
=
a
−
k
more=a-k
more=a−k,那么ZGY只需要超过
m
o
r
e
more
more个得分为
a
i
m
aim
aim或者
a
i
m
+
1
aim+1
aim+1的人就可以保住他的名次了。不好解释,想想这是为什么?
所以我们先处理掉这 m o r e more more个分数为 a i m aim aim或 a i m + 1 aim+1 aim+1的人,再在剩下的人中找 a i m + 1 − m o r e aim+1-more aim+1−more个人。当然, r p rp rp值还是要从小到大。
ll work2()//得分为aim+1分,要超过至少more个aim和aim+1的人
{
memset(vis,0,sizeof(vis));
ll qwq=more,tql=aim+1,ret=0;
for(ll i=1;i<=n&&qwq;++i)
{
if(tr[i].p==aim||tr[i].p==aim+1)
{
ret+=tr[i].e;
qwq--;
tql--;
vis[i]=1;
}
}
for(ll i=1;i<=n&&tql>0;++i)
{
if(vis[i])continue;
ret+=tr[i].e;
tql--;
}
return ret;
}
case3
当ZGY的得分刚好为
a
i
m
aim
aim时,这时只用考虑得分为
a
i
m
−
1
aim-1
aim−1或
a
i
m
aim
aim的人就可以了。设得分为
a
i
m
−
1
aim-1
aim−1的人有
t
q
r
tqr
tqr个(日膜
t
q
r
tqr
tqr),很明显,ZGY要超过
t
q
r
tqr
tqr个得分为
a
i
m
−
1
aim-1
aim−1或
a
i
m
aim
aim的人,同时还要超过
m
o
r
e
more
more个得分为
a
i
m
aim
aim的人,才能保证他的地位不被动摇。最后在找剩下的
a
i
m
−
m
o
r
e
−
t
q
r
aim-more-tqr
aim−more−tqr个人就可以了。
ll work3()//得分为aim分,要超过more个aim分和aim-1分个aim-1分或aim分的人
{
memset(vis,0,sizeof(vis));
ll qwq=more,tqr=0,tql=aim,ret=0;
for(ll i=1;i<=n;++i)
{
if(tr[i].p==aim-1)
tqr++;
}//记录aim-1分的人
for(ll i=1;i<=n&&qwq;++i)
{
if(tr[i].p==aim)
{
qwq--;
tql--;
ret+=tr[i].e;
vis[i]=1;
}
}//超过more个aim的人
for(ll i=1;i<=n&&tqr;++i)
{
if((tr[i].p==aim||tr[i].p==aim-1)&&(!vis[i]))
{
tqr--;
tql--;
ret+=tr[i].e;
vis[i]=1;
}
}//超过tqr个aim-1或aim分的人
for(ll i=1;i<=n&&tql>0;++i)
{
if(!vis[i])
{
ret+=tr[i].e;
tql--;
}
}
return ret;
}
当ZGY的得分小于 a i m aim aim时,显然不合题意啊。最后三个答案取最小值,即为最终结果。
代码
#include<cstdio>
#include<cstring>
#include<assert.h>
#include<algorithm>
#define ll long long
#define maxn 300005
using namespace std;
struct node
{
ll e,p;
}tr[maxn];
ll t,n,k,ans;
ll aim,more;//目标分数,多的人数
ll vis[maxn];
bool cmp1(node x,node y)
{
return x.p>y.p;
}
bool cmp2(node x,node y)
{
return x.e<y.e;
}
ll judge()
{
ll tot=0;
for(ll i=1;i<=n;++i)
if(tr[i].p>n)tot++;
if(tot>k-1)return 1;
return 0;
}
ll work1()//得分为aim+2分,无忧无虑
{
ll ret=0;
for(ll i=1;i<=aim+2&&i<=n;++i)
ret+=tr[i].e;
return ret;
}
ll work2()//得分为aim+1分,要超过至少more个aim和aim+1的人
{
memset(vis,0,sizeof(vis));
ll qwq=more,tql=aim+1,ret=0;
for(ll i=1;i<=n&&qwq;++i)
{
if(tr[i].p==aim||tr[i].p==aim+1)
{
ret+=tr[i].e;
qwq--;
tql--;
vis[i]=1;
}
}
for(ll i=1;i<=n&&tql>0;++i)
{
if(vis[i])continue;
ret+=tr[i].e;
tql--;
}
return ret;
}
ll work3()//得分为aim分,要超过more个aim分和aim-1分个aim-1分或aim分的人
{
memset(vis,0,sizeof(vis));
ll qwq=more,tqr=0,tql=aim,ret=0;
for(ll i=1;i<=n;++i)
{
if(tr[i].p==aim-1)
tqr++;
}//记录aim-1分的人
for(ll i=1;i<=n&&qwq;++i)
{
if(tr[i].p==aim)
{
qwq--;
tql--;
ret+=tr[i].e;
vis[i]=1;
}
}//超过more个aim的人
for(ll i=1;i<=n&&tqr;++i)
{
if((tr[i].p==aim||tr[i].p==aim-1)&&(!vis[i]))
{
tqr--;
tql--;
ret+=tr[i].e;
vis[i]=1;
}
}//超过tqr个aim-1或aim分的人
for(ll i=1;i<=n&&tql>0;++i)
{
if(!vis[i])
{
ret+=tr[i].e;
tql--;
}
}
return ret;
}
int main()
{
freopen("tournament.in","r",stdin);
freopen("tournament.out","w",stdout);
scanf("%lld",&t);
while(t--)
{
memset(vis,0,sizeof(vis));
ans=0;
scanf("%lld%lld",&n,&k);
for(ll i=1;i<=n;++i)
scanf("%lld%lld",&tr[i].p,&tr[i].e);
if(k==n+1)
{
printf("0\n");
continue;
}
if(judge())
{
printf("-1\n");
continue;
}
sort(tr+1,tr+1+n,cmp1);//按得分排序
aim=tr[k].p;
more=1;
while(tr[k+more].p==tr[k].p)more++;
sort(tr+1,tr+1+n,cmp2);//按rp值排序
ans=min(min(work1(),work2()),work3());//分类贪心
printf("%lld\n",ans);
}
return 0;
}
总结
这道题思维难度有点高,能想到分类就是本题的突破口。但是怎么想到分类还需多加练习。
T3 ZGY的早餐
【题目描述】
ZGY 每天早上要从宿舍走路到机房,顺便从学校小卖部购买早饭,当然机智的 ZGY 一 定会走最短路。
学校的路可以看成一无向联通张图,图上有 n 个点,m 条边,每一个点都有一个唯一的 编号 1~n,每一条边有一个边权,表示两个点之间的距离,ZGY 的宿舍在 S 点,机房在 T 点,而小卖部在 H 点。
现在 ZGY 想知道从宿舍经过小卖部到达机房的最短距离,不过因为在这个世界上有 Q 个 ZGY,所以你必须回答 Q 个问题。
【输入格式】
第一行包含三个正整数 T, n , m 表示这是第 T 个数据图上有 n 个点 m 条边
接下来 m 行, 每行有三个整数 u, v, w 表示点 u 与 v 之间有一条长度为 w 的边(题 目保证不存在自环、重边)
接下来一行,包含一个整数 Q 表示 ZGY 的个数 接下来 Q 行,每行三个整数 S, H, T 分别表示宿舍、小卖部、机房所在的点的编号
【输出格式】
对于每一个询问 Q,输出一行,表示对于这个询问的答
解析
这道题我特地贴出来了数据规模,说明数据规模很重要。
乍一看第8,9,10个数据点的 n ≤ 1 0 5 n\leq10^5 n≤105,就发现没有最短路算法能胜任。但仔细看右边的其他,图连通无环,不就是棵树吗???好一个文字游戏,然而我还是被骗到了。
所以本题的算法:数据分治。
对于数据点1~5,直接用Floyed求解。
对于数据点6~10,用类似于倍增求lca的算法,求出S到H的距离,再求出H到T的距离,加起来就可以了。 这是这次考试最水的题。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define maxn 1005
#define maxm 250005
using namespace std;
struct node
{
ll v,next;
ll val;
}tr[maxm<<1];
ll t,n,m,q;
ll dis[maxn][maxn];
ll tot,head[maxm];
ll dep[maxm],f[maxm][25],sum[maxm][25];
void add(ll x,ll y,ll z)
{
tot++;
tr[tot].v=y;
tr[tot].val=z;
tr[tot].next=head[x];
head[x]=tot;
}
void floyed()
{
for(ll i=1;i<=n;++i)
dis[i][i]=0;
for(ll k=1;k<=n;++k)
{
for(ll i=1;i<=n;++i)
{
for(ll j=1;j<=n;++j)
{
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
}
void ready(ll x,ll fa)
{
dep[x]=dep[fa]+1;
for(ll i=0;i<=20;++i)
{
f[x][i+1]=f[f[x][i]][i];
sum[x][i+1]=sum[x][i]+sum[f[x][i]][i];
}
for(ll t=head[x];t;t=tr[t].next)
{
ll y=tr[t].v,z=tr[t].val;
if(y==fa)continue;
f[y][0]=x;
sum[y][0]=z;
ready(y,x);
}
}
ll lca(ll x,ll y)
{
ll ret=0;
if(dep[x]<dep[y])swap(x,y);
for(ll i=20;i>=0;--i)
{
if(dep[f[x][i]]>=dep[y])
{
ret+=sum[x][i];
x=f[x][i];
}
if(x==y)return ret;
}
for(ll i=20;i>=0;--i)
{
if(f[x][i]!=f[y][i])
{
ret+=sum[x][i]+sum[y][i];
x=f[x][i];
y=f[y][i];
}
}
ret+=sum[x][0]+sum[y][0];
return ret;
}
void work1()
{
for(ll i=1;i<=m;++i)
{
ll x,y;
ll z;
scanf("%lld%lld%lld",&x,&y,&z);
dis[x][y]=min(dis[x][y],z);
dis[y][x]=min(dis[y][x],z);
add(x,y,z);
add(y,x,z);
}
floyed();
scanf("%lld",&q);
for(ll i=1;i<=q;++i)
{
ll x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
printf("%lld\n",dis[x][y]+dis[y][z]);
}
}
void work2()
{
for(ll i=1;i<=m;++i)
{
ll x,y;
ll z;
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
ready(1,0);
scanf("%lld",&q);
for(ll i=1;i<=q;++i)
{
ll x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
printf("%lld\n",lca(x,y)+lca(y,z));
}
}
int main()
{
freopen("mindis.in","r",stdin);
freopen("mindis.out","w",stdout);
memset(dis,0x3f3f3f3f,sizeof(dis));
scanf("%lld%lld%lld",&t,&n,&m);
if(t<=6)
work1();//前6个点,floyed
else
work2();
return 0;
}
总结
认真读题(雾
最后,这次考试的算法范围应该都在noip的难度以内,但是思维难度较大。所以平常还是要多接触题。