【DFS】【最短路】【队列+树上差分+树上DP】Day 9.5

T1

送外卖 

题目描述 n 个小区排成一列,编号为从 0 到 n-1 。一开始,美团外卖员在第 0 号小区,目标为位于 第 n-1 个小区的配送站。
给定两个整数数列 a[0]~a[n-1] 和 b[0]~b[n-1] ,在每个小区 i 里你有两种选择:
1) 选择 a:向前 a[i] 个小区。
2) 选择 b:向前 b[i] 个小区。
把每步的选择写成一个关于字符 ‘a’ 和 ‘b’ 的字符串。求到达小区 n-1 的方案中,字 典序最小的字符串。如果做出某个选择时,你跳出了这 n 个小区的范围,则这个选择不合 法。 
• 当没有合法的选择序列时,输出 “No solution!”。
• 当字典序最小的字符串无限长时,输出 “Infinity!”。
• 否则,输出这个选择字符串。
 
字典序定义如下:串 s 和串 t,如果串 s 字典序比串 t 小,则
• 存在整数 i ≥ -1,使得∀j,0 ≤ j ≤ i,满足 s[j] = t[j] 且 s[i+1] < t[i+1]。
• 其中,空字符 < ‘a’ < ‘b’。
 
输入格式
输入有 3 行。
第一行输入一个整数 n (1 ≤ n ≤ 10^5)。
第二行输入 n 个整数,分别表示 a[i] 。
第三行输入 n 个整数,分别表示 b[i] 。
−n ≤ a[i], b[i] ≤ n
 
输出格式
输出一行字符串表示答案。
 
样例输入
7 5 -3 6 5 -5 -1 6 -6 1 4 -2 0 -2 0
样例输出
abbbb
 
数据范围及约定
对于 20%的数据,1 ≤ n ≤ 20
对于 50%的数据,1 ≤ n ≤ 500
对于另外 20%的数据,b[i]=0
对于 100%的数据,1 ≤ n ≤ 10^5,−n ≤ a[i], b[i] ≤ n

可以证明每个点只会走一次

如果遇到环就在环的开始点打上标记

找到可行路的时候回溯,如果回溯时碰到有标记的点就输出Infinity!,否则直接输出ans就行

 1 #include <cstdio>
 2 int n,a[100010],b[100010],use[100010],in[100010],flag=0,incircle[100010];
 3 char ans[100010];
 4 int dfs(int x,int dep)
 5 {
 6     if (x<0||x>n-1) return 0;
 7     if (x==n-1){ans[dep]='\0';flag=1;return 1;}
 8     if (in[x]){incircle[x]=1;return 0;}
 9     if (use[x]) return 0;
10     
11     use[x]=1;in[x]=1;
12     ans[dep]='a';
13     if (dfs(x+a[x],dep+1))
14     {
15         if (incircle[x]==1) flag=2;
16         return 1;
17     }
18     ans[dep]='b';
19     if (dfs(x+b[x],dep+1))
20     {
21         if (incircle[x]==1) flag=2;
22         return 1;
23     }
24     in[x]=0;
25     return 0;
26 }
27 int main()
28 {
29     scanf("%d",&n);
30     for (int i=0;i<n;i++) scanf("%d",&a[i]);
31     for (int i=0;i<n;i++) scanf("%d",&b[i]);
32     dfs(0,0);
33     if (flag==0) printf("No solution!");
34     if (flag==1) printf("%s",ans);
35     if (flag==2) printf("Infinity!");
36 }

T2

道路

题目描述
给定一个 n 个点 m 条无向边的连通图,每条边长度均为 1。
现在要求你删除尽量多的边,使得从 s1 到 t1 的最短路径不超过 l1,并且 s2 到 t2 的最短路 径不超过 l2。
如果没有满足条件的方案,输出-1。
 
输入格式
第一行输入两个正整数 n,m 表示无向图的点数和边数。
接下来 m 行每行两个整数 a,b 表示节点 a 和节点 b 之间有一条边(点从 1 开始编号) 。
接下来一行三个整数 s1,t1,l1。
接下来一行三个整数 s2,t2,l2。
1<=si,ti<=n 0<=li<=n
输入数据保证图连通并且没有重边和自环。
 
输出格式
输出一行一个整数,表示最多能够删多少条边,如果没有合法方案输出-1。
 
样例输入
5 4 1 2 2 3 3 4 4 5 1 3 2 3 5 2
样例输出
0

样例输入
5 4 1 2 2 3 3 4 4 5 1 3 2 2 4 2
样例输出
1

样例输入
5 4 1 2 2 3 3 4 4 5 1 3 2 3 5 1
样例输出
-1

数据范围及约定
对于 20%的数据,1<=n<=5
对于另外 30%的数据,s1=s2,t1=t2,l1=l2
对于 100%的数据,1<=n<=3000, n-1<=m<=min(3000, n*(n-1)/2)

由于这是一个稀疏图,所以可以直接跑3000次SPFA

枚举公共路径的两个端点即可,但是要考虑直接走两条最短路更优的情况,所以要初始化答案为两条最短路的路径和

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 struct E{
 6     int next,to;
 7 }e[9000001];
 8 int n,m,a,b,sz=0,s1,t1,l1,s2,t2,l2,head[3001],x1,x2,f[3001][3001],que[3001],in[3001],top=-1,end=0,ans;
 9 void push(int x)
10 {
11     top++;
12     if (top==3001) top=0;
13     que[top]=x;
14 }
15 int pop()
16 {
17     int ans=que[end];
18     end++;
19     if (end==3001) end=0;
20     return ans;
21 }
22 bool empty()
23 {
24     return top+1==end;
25 }
26 void insert(int a,int b)
27 {
28     sz++;
29     e[sz].next=head[a];
30     head[a]=sz;
31     e[sz].to=b;
32 }
33 int spfa(int s)
34 {
35     push(s);f[s][s]=0;
36     while(!empty())
37     {
38         int x=pop();in[x]=0;
39         for (int i=head[x];i;i=e[i].next)
40         {
41             if (f[s][e[i].to]>f[s][x]+1)
42             {
43                 f[s][e[i].to]=f[s][x]+1;
44                 if (!in[e[i].to])
45                 {
46                     in[e[i].to]=1;
47                     push(e[i].to);
48                 }
49             }
50         }
51     }
52 }
53 int main()
54 {
55     scanf("%d%d",&n,&m);
56     for (int i=1;i<=m;i++)
57     {
58         scanf("%d%d",&a,&b);
59         insert(a,b);insert(b,a);
60     }
61     scanf("%d%d%d",&s1,&t1,&l1);
62     scanf("%d%d%d",&s2,&t2,&l2);
63     memset(f,0x7f,sizeof(f));
64     for (int i=1;i<=n;i++) spfa(i);
65     if (f[s1][t1]>l1||f[s2][t2]>l2)
66     {
67         printf("-1");
68         return 0;
69     }
70     ans=f[s1][t1]+f[s2][t2];
71     for (int i=1;i<=n;i++) for (int j=1;j<=n;j++)
72     {
73         ans=min(ans,f[s1][i]+f[s2][i]+f[i][j]+f[j][t1]+f[j][t2]);
74         ans=min(ans,f[s1][i]+f[s2][j]+f[i][j]+f[j][t1]+f[i][t2]);
75     }
76     printf("%d",m-ans);
77 }

T3

集合
 
题目描述
给定一棵 n 个点的树,每个点有点权。
现在要求你统计满足如下条件的点集 S 的数量:
1. 点集 S 非空
2. 点集 S 中的点在树上是一个连通块,即如果 u 和 v 在集合 S 中,则从 u 到 v 经过的所有 点均在 S 中
3. 点集 S 中最大点权与最小点权的差值不超过 d
 
输出满足上面条件的点集数量求余 1e9+7。
 
输入格式
第一行输入两个整数 d 和 n,含义见题目描述。
第二行输入 n 个正整数 ai 表示每个点的点权。
接下来 n-1 行每行两个正整数 u,v 描述一条边。
点从 1 开始编号。
 
输出格式
输出一个数表示答案。
 
样例输入
1 4 2 1 3 2 1 2 1 3 3 4
样例输出
8

样例输入
0 3 1 2 3 1 2 2 3
样例输出
3

样例输入
4 8 7 8 7 5 4 6 4 10 1 6 1 2 5 8 1 3 3 5 6 7 3 4
样例输出
41

样例说明
样例一中满足条件的集合为:{1} {2} {3} {4} {1, 2} {1, 3} {3, 4} {1, 3, 4}
 
数据范围及约定
对于 20%的数据,1 ≤ n ≤ 20
对于另外 20%的数据,所有点的点权均相同
对于另外 20%的数据,d=0
对于 100%的数据,0 ≤ d ≤ 2000,1 ≤ n ≤ 2000,1 ≤ 点权 ≤ 2000
 

队列+树上差分

挺有难度的一道题

先按点权把所有点从小到大排序,再枚举区间右端点并维护一个左端点和右端点点权之差不超过d的队列,每次检查由队列中的点所组成的包含右端点的连通块的方案数,对右端点的各个分支递归使用乘法原理即可求出方案数

为什么集合一定要包含右端点呢?因为只有单调的决策(就是指从小到大枚举区间右端点且连通块一定要包含右端点,具体还是自己意会吧)才能保证没有方案没有被重复计数

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <iostream>
 5 #define MOD 1000000007
 6 #define ll long long
 7 using namespace std;
 8 struct P{
 9     ll n,a;
10 }p[2001];
11 struct E{
12     ll next,to;
13 }e[4001];
14 ll d,n,a,b,end=1,sz=0,ans=0,flag[2001],reg[2001],head[2001];
15 bool cmp(P a,P b){return a.a<b.a;}
16 void insert(ll a,ll b)
17 {
18     sz++;
19     e[sz].next=head[a];
20     head[a]=sz;
21     e[sz].to=b;
22 }
23 ll dfs(ll f,ll x)
24 {
25     if (flag[x]==0) return 0;
26     ll s=1;
27     for (ll i=head[x];i;i=e[i].next)
28         if (e[i].to!=f) s=s*(dfs(x,e[i].to)+1)%MOD;
29     return s;
30 }
31 int main()
32 {
33     ios::sync_with_stdio(false);
34     cin>>d>>n;
35     for (ll i=1;i<=n;i++) p[i].n=i,cin>>p[i].a;
36     for (ll i=1;i<n;i++)
37     {
38         cin>>a>>b;
39         insert(a,b);
40         insert(b,a);
41     }
42     sort(p+1,p+1+n,cmp);
43     for (ll i=1;i<=n;i++)
44     {
45         flag[p[i].n]=1;
46         while(p[end].a<p[i].a-d) flag[p[end].n]=0,end++;
47         ans=(ans+dfs(p[i].n,p[i].n))%MOD;
48     }
49     cout<<ans<<endl;
50 }

 

转载于:https://www.cnblogs.com/algonote/p/7486273.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值