2021-1-18[B]总结

这是一个好东西->作者主页

T1 links

题目1-1

题目大意:给你一棵无根树,让你求一个节点和与它相距2的节点的乘积和以及最大值
记得 m o d mod mod 乘积和,不用 m o d mod mod 最大值

比赛时思路:
用vector将图存下来,枚举每一个点,再枚举这个节点的儿子,再枚举这个儿子的儿子(也就是孙子),
因为边是双向的,所以这个点的孙子的时候,可能自己就变成孙子了,为了不让它谋权篡位(谁想当孙子),
要判断 i ! = e [ e [ i ] [ j ] ] [ k ] i!=e[e[i][j]][k] i!=e[e[i][j]][k](i是父亲,j是儿子,k是孙子),
然后把 w [ i ] ∗ w [ e [ e [ i ] [ j ] ] [ k ] ] w[i]*w[e[e[i][j]][k]] w[i]w[e[e[i][j]][k]]取个 m a x max max 再累加起来就可以拿到 60 60 60分。

我们考虑一下优化,枚举的时候是从小到大枚举的,那么爷爷的编号一定比孙子的编号要小,如果爷爷的编号比孙子的编号要大,那么在之前这一对就已经算过了,重复的就不需要再算了
稍微改一下,把判断条件改为 e [ e [ i ] [ j ] ] [ k ] > i e[e[i][j]][k]>i e[e[i][j]][k]>i在累加的时候把 当 前 的 乘 积 ∗ 2 当前的乘积*2 2就行了,得分变成了70。时间复杂度在极端数据会变成 O ( n 2 ) O(n^2) O(n2)

正解:优化后还是不能满分说明上面的思路肯定不是正解。于是我们想另一种做法。
因为每条边都是双向的,所以我们改变一下,枚举每一个点作为中转站,这样,只需要将它的儿子两两相乘就行了。
但是这还是会超时,于是我们推一下式子吧

当这个点为 a a a,它的儿子为 b , c , d b,c,d b,c,d权值为 B , C , D B,C,D B,C,D
那么所有的和就是 2 B C + 2 B D + 2 C D 2BC+2BD+2CD 2BC+2BD+2CD
化简一下就得到 ( B + C + D ) 2 − ( B 2 + C 2 + D 2 ) (B+C+D)^2-(B^2+C^2+D^2) (B+C+D)2(B2+C2+D2)
也就是将所有儿子的权值加起来平方一下再减去每个儿子平方的和,再全部都累加起来就是最后要求的总和。
Attention! 记得算的过程中要取m模!

最大乘积很简单,就是每次枚举儿子的时候拿两个变量维护一个最大值和一个次大值,相乘取个 m a x max max就行了。

#include<bits/stdc++.h>
using namespace std;
int n,x,y,z,ans,maxn,max1,max2,q,p,l,v;
vector<int> e[200005]; int w[200005];
//纪中训练的小伙伴请不要用vector,这里博主是为了方便,你们应该手打
inline int read()
{
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
   return s*w;
}

int main()
{
	n=read();
	for(register int i=1;i<n;i++)
	{
		x=read(); y=read();
		e[x].push_back(y); e[y].push_back(x);
	}
	for(register int i=1;i<=n;i++) w[i]=read();
	for(register int i=1;i<=n;i++)
	{
		l=e[i].size(); 
		max1=-9999999; max2=-9999999; q=0; p=0;
		if(l>1)
		{
			for(register int j=0;j<l;j++)
			{
				v=e[i][j];
				p=(p+w[v])%10007; q=(q+w[v]*w[v])%10007;
				if(w[v]>=max1) 
					max2=max1,max1=w[v];
				if(w[v]<max1&&w[v]>=max2) 
					max2=w[v];
			}
			ans=(ans+p*p-q)%10007;
			maxn=max(maxn,max1*max2);
		}
	}
	printf("%d %d",maxn,ans);
	return 0;
}

T2 寻找道路

题目2-1

如果没有1的约束条件、那么这题也不过是在图中求最短路径的问题!而且,又因为是无权图所以只要bfs一下就可以搜索出来了!!

但是很遗憾,他有约束条件。

我们不难看出,条件一目的是排除一些点。也就是不满足条件一的点不能走。

那关键就是在于如何找到不满足条件一的点!!!
其实很简单

条件一:路径上的所有点的出边所指向的点都直接或间接与终点连通。

也就是说对于点node0,他的子节点node1,node2,node3,node4,node5…
全部都可以到达end(目标节点)

那我们怎么办呢?
先找出所有可以到达end的点,对他们进行标记。再对每个节点node0进行遍历,看它的子节点,如果有一个子节点未被标记,那么被遍历的该节点node0失格。
很简单的思路!(其实这里我们只需遍历可以到达end的点即被标记的点就可以了)
如何找到,所有可以到达end的点呢?
在记录边时反向记一份,再以end为起点进行一次搜索,找到所有能抵达的点,对他们进行标记。(因为图中有环,所以这里我们也是选择使用bfs)

具体过程如下

  1. 首先预处理,反向建图,从终点出发,标记所有终点能够直接或间接到达的点。
  2. 然后遍历所有的点,未被标记的点也就是原来图中直接或间接到达不了终点的点。
  3. 依据题意,与这些点直接相连的那些被标记的点现在就不能走了。你会怎么标记呢?这里要注意后效性。原来标记了假设为vis[i]=1;现在i点因为与不能到达终点的某个点相连,应该取消标记。但取消标记了后的含义就是i这个点不能到达终点了,这样与i相连的就也都被取消标记了…但这显然是错的,因为i是可以到达终点的。用另外一个标记数组vis2在第一遍bfs后memcpy一下vis数组,然后跟据vis的情况对vis2进行调整就避免了后效性。
  4. 在被标记的点中bfs去求单源最短路。
#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
vector<int> G[maxn];
//纪中训练的小伙伴请不要用vector,这里博主是为了方便,你们应该手打
int n, m, s, t;
queue<int> q;
//手打队列也是训练你们代码能力的好东西
int vis[maxn];
int vis2[maxn];
int dis[maxn];
void bfs()
{
    q.push(t);
    vis[t]=1;
    while(!q.empty()) {
        int tmp = q.front();
        q.pop();
        for(int i = 0; i < G[tmp].size(); i++) {
            if(vis[G[tmp][i]]) continue;
            vis[G[tmp][i]]=1;
            q.push(G[tmp][i]);
        }
    }
}
int bfs2() {
    while(!q.empty()) q.pop();
    q.push(t);
    dis[t] = 0;
    while(!q.empty()) {
        int tmp = q.front();
        q.pop();
        for(int i = 0; i < G[tmp].size(); i++) {
            if(vis2[G[tmp][i]] == 0) continue;
            if(dis[G[tmp][i]] != -1) continue;
            dis[G[tmp][i]] = dis[tmp]+1;
            if(G[tmp][i] == s) return 1;
            q.push(G[tmp][i]);
        }
    }
    return 0;
}
int main()
{
    memset(vis, 0, sizeof(vis));
    memset(dis, -1, sizeof(dis));
    //memset请注意,尽量手打
    scanf("%d%d",&n,&m);
    while(m--){
        int x, y;
        scanf("%d%d",&x,&y);
        G[y].push_back(x);  //反向建边
    }
    scanf("%d%d",&s,&t);
    bfs();
    memcpy(vis2, vis, sizeof(vis));
    for(int i = 1; i <= n; i++) {
        if(vis[i] == 0) {
            for(int j = 0; j < G[i].size(); j++) {  //后效性注意!
                vis2[G[i][j]] = 0;
            }
        }
    }
    if(bfs2()) {
        printf("%d\n",dis[s]);
    }
    else {
        printf("-1\n");
    }
}

T3 Flappy Birds

题目3-1

怎么说,第一眼看过去差点没把我吓到。
然后第二眼,我看到了:
在这里插入图片描述
我想到了“正解”;
可谓RP爆棚,打了个rand()随机数,如下:
在这里插入图片描述

喜获(10)分。
然而,我也将桌面屏幕改成了这个
在这里插入图片描述
————————————————————————————————————————————————
然而正解是这样的:

首先,在第i列(i!=0)时,可从第i-1列降下来,也可从第i-1列蹦很多次。设 f [ i ] [ j ] f[i][j] f[i][j]为蹦到(i,j)的最少次数,则:

降的过程可这样实现: f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i − 1 ] [ j + u n c l [ i ] ] ) f[i][j]=min(f[i][j],f[i-1][j+uncl[i]]) f[i][j]=min(f[i][j],f[i1][j+uncl[i]])当然,这是在前后两点都可以到的情况下(即没有碰到地面或柱子)才能这样做。

升的过程可以用类似完全背包的做法: f [ i ] [ j ] = m i n ( f [ i ] [ j ] , m i n ( f [ i − 1 ] [ j − c l i c [ i ] ] + 1 , f [ i ] [ j − c l i c [ i ] ] + 1 ) ) f[i][j]=min(f[i][j],min(f[i-1][j-clic[i]]+1,f[i][j-clic[i]]+1)) f[i][j]=min(f[i][j],min(f[i1][jclic[i]]+1,f[i][jclic[i]]+1))这也是要前提的,同上。

当然当小鸟顶到天花板上时,还有情况,即小鸟本应该再上升,可是再上升要越界了,

即: f o r ( i n t j = m ; j > m − c l i c [ i ] ; j − − ) f [ i ] [ m ] = m i n ( f [ i ] [ m ] , m i n ( f [ i − 1 ] [ j ] + 1 , f [ i ] [ j ] + 1 ) ) ; for (int j=m; j>m-clic[i]; j--) f[i][m]=min(f[i][m],min(f[i-1][j]+1,f[i][j]+1)); for(intj=m;j>mclic[i];j)f[i][m]=min(f[i][m],min(f[i1][j]+1,f[i][j]+1));

那么,最后扫一下当前列是否有解,最后扫一下就好了。

然后,提交了n次,80分,80分,80分,80分,80分,80分,80分,80分,……
急了!哪里错了?

Attention!当小鸟降落下来就无法在这一时间再升上去了!所以怎么办?先写升的过程再写降的过程就好了。

当然,这里细节很多,特别是上下边界,哪些更新到应该非常清楚才行。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10005,maxm=1005;
int n,m,p;
int clic[maxn],uncl[maxn],L[maxn],H[maxn];
int f[maxn][maxm],cnt,INF;
inline int read(){
    int x=0; char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
int main(){
    n=read(),m=read(),p=read();
    for (int i=1; i<=n; i++) clic[i]=read(),uncl[i]=read();
    for (int i=1; i<=n; i++) L[i]=0,H[i]=m+1;
    for (int i=1; i<=p; i++){
        int x=read(); L[x]=read(),H[x]=read();
    }
    memset(f,63,sizeof f),INF=f[0][0],cnt=0;
    for (int i=0; i<=m; i++) f[0][i]=0;
    for (int i=1; i<=n; i++){
        for (int j=1; j<H[i]; j++) if (j>=clic[i]&&j!=m)
        f[i][j]=min(f[i][j],min(f[i-1][j-clic[i]]+1,f[i][j-clic[i]]+1));
        for (int j=1; j<H[i]; j++) if (j+uncl[i]<=m&&j>=L[i]+1)
        f[i][j]=min(f[i][j],f[i-1][j+uncl[i]]);
        if (H[i]==m+1)
            for (int j=m; j>=max(1,m-clic[i]); j--) f[i][m]=min(f[i][m],min(f[i-1][j]+1,f[i][j]+1));
        bool flythr=0;
        for (int j=L[i]+1; j<H[i]; j++) if (f[i][j]<INF){flythr=1; break;}
        for (int j=0; j<=L[i]; j++) f[i][j]=INF;
        if (!flythr){printf("0\n%d",cnt); return 0;}
        if (L[i]>0||H[i]<m) cnt++;
    }
    int ans=1<<30;
    for (int i=1; i<=m; i++) ans=min(ans,f[n][i]);
    printf("1\n%d",ans);
    return 0;
}

完成情况:

  • T1
  • T2
  • T3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值