2021/5/29 考试总结

本文探讨了动态规划在解决书架排列、命题逻辑、选座策略和施工路线问题中的应用。对于书架问题,通过黑白交替的限制,利用DP求解方案数。命题逻辑中,通过拓扑排序确定公理数量和最少推导次数。选座问题中,第一种方案要求最大座位数,第二种方案是覆盖最多的旅客。施工问题中,分析最短路径受影响情况。
摘要由CSDN通过智能技术生成

1.小W与书架

2.小W与命题

3. 小W与选座

4.  小W与施工

总结:


1.小W与书架

目录

架上从左往右放上书。

他有两种书,第一种厚度是 1,第二种厚度是 h。他的书有两种颜色,黑色和白色。黑色的书可以是 1或 h,白色的书厚度只能是 1。出于美观的目的,他放书必须按照黑白交替放,并且最左边和最右边都要是黑色。他想知道他有多少种方案放书。他必须至少放一本书。最左边的书必须放在书架最左边。

由于答案可能非常大,请输出答案对 998244353 取模后的结果。

输入格式

一行两个整数 L,h。

输出格式

一行一个整数表示答案。

样例数据

input

4 2

output

5

input

100 7

output

19795286

数据规模与约定

对于 20% 的数据,满足 L≤10。

对于另外 10% 的数据,满足 h=L。

对于 100% 的数据,满足 2≤h≤L≤1e6。

时间限制:1s

空间限制:512MB

解题思路

一看就知道是DP

思路有很多种,就介绍一下我的

设 f[i][0/1] 表示 在第 i 个位置上是以黑(0) 或白(1)书结尾的方案数

状态转移

f[i][0]=f[i][0]+f[i-1][1]+f[i-h][1]

f[i][1]=f[i-1][0]

其中i-h 大于0.

完整代码

#include<bits/stdc++.h>
using namespace std;
int l,h;
long long f[1200000][2];
long long ans=0;
int main()
{
	cin>>l>>h;
	f[1][0]=1;
	f[h][0]=1;
	for(int i=1;i<=l;i++)
	{
		f[i][0]=(f[i][0]+f[i-1][1])%998244353;
		if(i-h>=0) f[i][0]=(f[i][0]+f[i-h][1])%998244353;
		f[i][1]=(f[i][1]+f[i-1][0])%998244353;
	}
	for(int i=1;i<=l;i++)
	ans=(ans+f[i][0])%998244353;
	cout<<ans;
	return 0;
} 

考试得分:100分

2.小W与命题

题目描述

小W在上数理逻辑的课。他想要自创一套公理系统。

现在小W有 n 个命题,标号为 1−n,他有 m 个推出关系。每个推出关系都是形如 P⇒Q,意思是如果 P 被证明是对的,那么就可以推出 Q 是对的。你可以把这些推出关系想成一个有向图。他发现这张图没有环。他现在想要从中选出一些命题设为公理,要求其它所有命题都可以通过这些公理推出。他想要最小化选出的公理数,并输出任意一种方案即可。

小W给出公理之后,小W的朋友小Y想要通过这些公理去证明所有命题。对于每一个命题 TT,他想要用最少的推导次数证明这个命题。即找一条路径 v1,v2,…,vk=T,满足 v1 是一条公理,并且 ∀ i∈[1,k−1], vi⇒vi+1,则推导次数是 k−1。

输入格式

第一行两个整数 n,m。 接下来 m 行,每行两个整数 P,Q,表示第 P 个命题可以推出第 Q 个命题。

输出格式

第一行一个整数 k,表示公理的个数。 第二行 k 个整数,表示选出的公理,按从小到大输出。 第三行 n 个整数,第 i 个整数表示第 i 个命题的最少推导次数。

样例数据

input

7 6
1 3
2 3
3 4
3 5
4 6
5 6

output

3
1 2 7
0 0 1 2 2 3 0

 

数据规模与约定

对于 20% 的数据,n,m≤15。

对于 60% 的数据,n,m≤1000。

对于 100% 的数据,1≤n,m≤5∗1e5。

时间限制:2s

空间限制:512MB

解题思路

入读为0的点就是公理,一遍拓扑排序可以求出到每个点的最小距离

#include<bits/stdc++.h>
using namespace std;
const int maxn =5e5+7;
struct node
{
	int y;
	int next;
}e[maxn];
int indo[maxn],tim[maxn],link[maxn];
int n,m,top,cnt,ans[maxn];
bool vis[maxn];
queue<int> q;
void Insert(int x,int y)
{
	e[++top].y=y;
	e[top].next=link[x];
	link[x]=top;
}
void topsort()
{
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=link[x];i;i=e[i].next)
		{
			int y=e[i].y;
			if(indo[y]==0) continue;
			indo[y]--;
			tim[y]=min(tim[y],tim[x]+1);
			if(indo[y]==0)
			{
				q.push(y);	
			}

		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		Insert(x,y);
		indo[y]++;

	}
	for(int i=1;i<=n;i++)
		tim[i]=99999999;	
	for(int i=1;i<=n;i++)
	{
		if(indo[i]==0) 
		{
			tim[i]=0;
			ans[++cnt]=i;	
			q.push(i);
		}
	}
	topsort();
	cout<<cnt<<endl;
	for(int i=1;i<=cnt;i++)
	printf("%d ",ans[i]);
	cout<<endl;
	for(int i=1;i<=n;i++)
	printf("%d ",tim[i]);
	return 0;
}

考试时有一处n和m打反了,很亏

考试得分:95分

3. 小W与选座

题目描述

小W作为高铁设计者,他现在遇到了些难题。 现在他想要设计一条线路的高铁,这趟高铁停 1e5 个站,我们将它编号为 1,2,…,1e5。其中 1 是起点,1e5 是终点。 现在这趟车有 n 个旅客。每个旅客从 si上车,在 ti 下车。保证 si<ti。现在这 n 个旅客按照随机的顺序分别在网上订票。现在有两种订票的方式。第一种是每个人都可以在订票的时候自己选择座位,选的座位要求在 si 到 ti 中都没人选过,第二种是系统在所有 n 个人订完票之后安排座位。每个人要求行程中不换座位,且每个座位在任意时刻最多只能分配给一个人。当然,高铁中的座位数必须满足在任意订票顺序的情况下,都能够满足所有人的订票需求。例如现在有三个人,(si,ti) 分别为(1,2),(1,3),(2,3)。那么按照第一种方案,则必须准备至少 3 个座位,因为假设只有 2 个座位,如果按照 1,3,2 的顺序订票,并且第 1 个人选了座位 1,第 3 个人选了座位 2,那么第 2 个人来选的时候就没座位可选了。而如果按第二种方案的话,则 2 个座位就够了,把 1,3 安排到座位 1,把 2 安排到座位 2 即可。 现在小W想知道,对于两种方案,他分别需要至少安排多少个座位。

输入格式

第一行一个整数 n。 接下来 n 行每行两个整数 si,ti。

输出格式

一行两个整数分别表示两种方案的答案。

样例数据

input

3
1 2
1 3
2 3

output

3 2

数据规模与约定

对于 15% 的数据,n≤5。

对于 25% 的数据,n≤10。 对于 50% 的数据,n≤2000。

对于 100%的数据,1≤n≤2∗1e5,1≤si<ti≤1e5。

时间限制:1s

空间限制:512MB

解题思路:

首先是第一问

如果想让座位数尽可能多,那么对于第i个人来说,此时他所需要的座椅数就是和他有交集的人的个数,这样是最优的,如果再多就浪费了。然后把每个人的值取一个最大值就是整个的最大值。

那么怎么对每个人求和他有相交的人数呢?

(考试时我写了一个N^2的双重循环,水了60分)

正解:

设sum1[i] 表示 从 1~i 的时间内中有多少人上过车(也就是多少个Si)

    sum2[i]表示 1~i 的时间 内有多少人下了车。

这两个数组可以前缀和求出

     那么对于第 i 个人来说 ,sum1[t[i]-1]-sum2[s[i]] (也就是上来的人数减掉下去的人数)就是现在还在车上的人数。再取max

然后第二问 是一道经典贪心题,答案就是覆盖旅客数最多的区间覆盖的旅客数。可以搜一下,我就不详写了。


#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
int sum1[N],sum2[N],sum3[N],sum4[N];
int sum5[N],sum6[N],a[N],b[N];
int main()
{
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	int n,t=-1;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[i]=x;b[i]=y;
		t=max(t,y);
		sum1[x]++;sum1[y]--;
		sum3[x]++;sum4[y]++;
	}
	int ans2=-1,ans1=-1;
	for(int i=1;i<=t;i++)
	{
		sum2[i]=sum2[i-1]+sum1[i];
		ans2=max(ans2,sum2[i]);
		sum5[i]=sum5[i-1]+sum3[i];
		sum6[i]=sum6[i-1]+sum4[i];
	}
	for(int i=1;i<=n;i++)
	{
		ans1=max(ans1,sum5[b[i]-1]-sum6[a[i]]); 
	}
	cout<<ans1<<" "<<ans2<<endl; 
	return 0;
}

考试得分:60

4.  小W与施工

题目描述

小W住在鸽城。鸽城一共有 n个路口,有 m 条单向的道路连接路口。每天小W要从 S 到 T 去上班。他总是走最短的一条路去上班。保证 S 能够到达 T。 由于鸽城的人太鸽了,所以很多道路都需要维修。鸽城发出通告说接下来 q 天内,鸽城每天将维修一条道路,第 i 天将维修第 di 条道路,在这一天内,第 di条道路将无法通行。小W想知道在第 i 天他上班会不会受到影响。他会受到影响当且仅当那天他上班的最短路将变长或者将无法上班。 注意允许有重边和自环。

输入格式

第一行五个整数 n,m,q,S,T。 接下来 m 行每行三个整数 u,v,w,表示一条从 u 到 v 的长度为 ww 的单向道路。 接下来一行 q 个整数,表示 di。

输出格式

输出 q 行,每行为 YES 或 NO,表示第 i 天的答案,YES 表示会受到影响,NO 表示不会。

样例数据

input

4 5 5 1 4
1 2 3
2 3 4
3 4 5
2 1 1
3 2 1
1 2 3 4 5

output

YES
YES
YES
NO
NO

input

4 4 1 4
1 2 1
2 4 1
1 3 1
3 4 1
1 2 3 4

output

NO
NO
NO
NO

 

数据规模与约定

对于 30%的数据,满足 n,m,q≤1000。

对于 100% 的数据,满足 1≤n,m,q≤1e5,1≤w≤1e9。

时间限制:1s

空间限制:512MB

解题思路:

30分暴力思路:每次询问都跑一遍dijkstra

100分:

方法一:最短路+DP

思考,对于一条边来说,如果这条边在整个图的最短路上,并且这个图的所有最短路都经过这条边,那么就有影响。

所以我们先用两边dij 求出每个点到起点的最短路dis[x]和到终点的最短路dis1[x](可以反向建图)

这样对于一条边(u,v,w)来说

只有dis[x]+w+dis1[y]=dis[T](仔细理解)那么这条边就在最短路上。

那么怎么判断图的所有最短路是否都经过这条边呢?

类似的,用cnt[x]表示起点到每个点的最短路个数,cnt1[x]表示每个点到终点的最短路个数

那么根据乘法原理,经过这条边到达终点的最短路条数就是cnt[x]*cnt1[y]

若这个数字和整个图的最短路个数cnt[T]相等,那么所有的最短路肯定都经过这条边

计算最短路个数可以看洛谷P1144 最短路计数

代码实现:

#include<bits/stdc++.h>
using namespace std;
#define mod 1000000007
const int M=5e5+7;
struct edge
{
	int y,v,next;
}e[2*M][3];
struct node
{
	int y;
	long long v;
};
struct zie
{
	int x,y,v;
}inf[M];
bool operator <(const node &a,const node &b)
{
	return a.v>b.v;
}
int link[M][2],vis[M],S,T;
long long dis[M][2],f[M][2];
int t[2],n,m,Q;
void Insert(int x,int y,int v,int p)
{
	e[++t[p]][p].y=y;
	e[t[p]][p].v=v;
	e[t[p]][p].next=link[x][p];
	link[x][p]=t[p];
}
priority_queue<node> q;
node c(int y,long long v)
{
	node f;
	f.y=y;
	f.v=v;
	return f;
}
void dij(int s,int t,int p)
{
	for(int i=1;i<=n;i++)
	{
		dis[i][p]=99999999999;
		vis[i]=0;
	}
	dis[s][p]=0;
	f[s][p]=1;
	q.push(c(s,0));
	while(!q.empty())
	{
		node temp=q.top();
		q.pop(); 
		int x=temp.y;
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=link[x][p];i;i=e[i][p].next)
		{
			int y=e[i][p].y;
			if(vis[y]) continue;
			if(dis[y][p]>dis[x][p]+e[i][p].v)
			{
				dis[y][p]=dis[x][p]+e[i][p].v;
				f[y][p]=f[x][p];
				q.push(c(y,dis[y][p]));
			}
			else if(dis[y][p]==dis[x][p]+e[i][p].v)
			{
				f[y][p]+=f[x][p];
				f[y][p]%=mod;
			}
		}
	}
}
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
int main()
{
	cin>>n>>m>>Q>>S>>T;
	for(int i=1;i<=m;i++)
	{
		int x,y,v;
		x=read();
		y=read();
		v=read();
		Insert(x,y,v,0);
		Insert(y,x,v,1);
		inf[i].x=x;
		inf[i].y=y;
		inf[i].v=v;
	}
	dij(S,T,0);
	dij(T,S,1);	
	while(Q--)
	{
		int d;
		d=read();
		int x=inf[d].x;
		int y=inf[d].y;
		int v=inf[d].v;
		if(dis[x][0]+v+dis[y][1]==dis[T][0]&&f[x][0]*f[y][1]%mod==f[T][0]%mod)
		printf("YES\n");
		else printf("NO\n");
	}
	return 0;
} 

方法二:最短路+Tarjan

刚才我们提到了“个图的所有最短路都经过这条边”,这个词似乎和割边有点关系。

事实上,如果我们把把所有在最短路上的点弄一起,建一个新图,就会得到一个新的最短路图。

建图方法与上文类似,枚举每一条边判断是否在最短路上,如果是就加进新图。

那么在新图上如何判断是否起点到终点的所有路径都经过这条边呢?

其实就是有Tarjan求一下割边。

因代码实现较复杂,我就不呈现了。

考试得分:30

总结:

前两题太水,后两题却不简单,总的来说还可以,但是第三题没有深入思考,下次应该注意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值