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 }