2021.10.14

T1:完美主义者 Makik

Problem:

有一说一,众所周知,Makik是一名完美主义者。这天,他来到某企业参观其城市地铁线路运营图。这个线路图一共 n 个中转站,一些站之间有线路相连,共有 m 条线路。为了贴合 Makik 的完美主义,线路的长度都为 1。且为双向道路。但是喜欢完美的 Makik 又问了一个问题,从某个中转站到另一个中转站能否有一条路径使得长度恰巧为 d 呢?每条边可以走多次。

Solution:

注意给定的是无向图,所以可以在一条边上来回摩擦来蹭奇偶性

所以这题其实就是求一个点到另外一个点的奇数的最短路和偶数的最短路

spfa

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int inf=0x3f3f3f3f;
 4 const int maxn=5005;
 5 vector<int>t[maxn];
 6 int n,m,k,tot,h[maxn];
 7 bool vis[maxn],ans[1000005];
 8 int dis[2][maxn];
 9 struct query{
10     int u,v,w;
11 }q[1000005];
12 struct node{
13     int nxt,to;
14 }e[maxn<<1];
15 inline void add(int u,int v){
16     e[++tot].nxt=h[u];
17     e[tot].to=v;
18     h[u]=tot;
19 }
20 inline void spfa(int s){
21     queue<int>Q;
22     for(int i=1;i<=n;i++){
23         dis[0][i]=inf;
24         dis[1][i]=inf;
25         vis[i]=0;
26     }
27     Q.push(s);
28     vis[s]=1;
29     dis[0][s]=0;
30     while(!Q.empty()){
31         int x=Q.front();
32         Q.pop();
33         vis[x]=0;
34         for(int i=h[x];i;i=e[i].nxt){
35             int xx=e[i].to,flg=0;
36             if(dis[0][x]!=inf){
37                 if(dis[1][xx]>dis[0][x]+1){
38                     dis[1][xx]=dis[0][x]+1;
39                     flg=1;
40                 }
41             }
42             if(dis[1][x]!=inf){
43                 if(dis[0][xx]>dis[1][x]+1){
44                     dis[0][xx]=dis[1][x]+1;
45                     flg=1;
46                 }
47             }
48             if(flg&&!vis[xx]){
49                 Q.push(xx);
50                 vis[xx]=1;
51             }
52         }
53     }
54 }
55 int main(){
56     scanf("%d%d%d",&n,&m,&k);
57     for(int i=1;i<=m;i++){
58         int u,v;
59         scanf("%d%d",&u,&v);
60         add(u,v);
61         add(v,u);
62     }
63     for(int i=1;i<=k;i++){
64         scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);
65         t[q[i].u].push_back(i);
66     }
67     for(int i=1;i<=n;i++){
68         if(!t[i].size()) continue;
69         spfa(i);
70         for(int j=0;j<t[i].size();j++){
71             int o=t[i][j];
72             int num=q[o].w%2;
73             if(q[o].w>=dis[num][q[o].v]&&h[i]) ans[o]=1;
74         }
75     }
76     for(int i=1;i<=k;i++){
77         if(ans[i]) printf("TAK\n");
78         else printf("NIE\n");
79     }
80     return 0;
81 }

T2:Makik 的大树

Problem:

完美主义者 Makik 过于完美,开始追求全球环境问题。于是他开始研究种树。
他有一棵树:n 个结点的树。这棵树非常巨大,以至于身手矫健的 Makik 可以在上面玩耍。这天他突然生出一个同样与路径有关的题目:需要找出 k 个点,设这 k 个点分别为 A1, A2, … Ak,因为 Makik 讨厌走路和跑步和大跳和引体向上和跳远,所以他想要这 k 个点中 A1到A2,A2到A3, …, 𝐴𝐴𝑘𝑘−1到Ak的最短路径长度的总和最小。那么,这个总和至少是多少呢?

Solution:

首先一个非常显然的结论:最后选出来的一定是一个联通子图(也是一棵树)

对于这棵树,显然是直径上的直走一遍,直径以外的边走两遍

f ( x , i , j ) 表示以 x 为根的子树,选了 i 个点,这 i 个点里面有 j 个点为直径的端点 (j=0,1,2)

枚举一下下面那个子树里面选了几个点,下面子树里面有几个直径的端点

枚举一下自己子树里面选了几个点,自己子树里面有几个直径的端点

然后用上面的量就知道这个点和他的儿子的连边到底在不上这个直径上,也就知道这条边算一遍还是算两遍

对应取 min 更新就行了

时间复杂度 O(n2)

这个题目最主要的问题在于枚举儿子的时候只能枚举到min(k,sizeson)

这样能保证它是n2

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=3005;
 4 const int inf=0x3f3f3f3f;
 5 int n,m,f[maxn][maxn][3],sz[maxn],tmp[maxn][3],ans;
 6 int tot=0,head[maxn];
 7 struct edge{
 8     int to,nxt,w;
 9 }e[maxn*2];
10 inline void add(int u,int v,int w){
11     e[++tot].to=v;
12     e[tot].w=w;
13     e[tot].nxt=head[u];
14     head[u]=tot;
15 }
16 inline void updata(int &x,int y){
17     x=min(x,y);
18 }
19 inline void dfs(int x,int fa){
20     sz[x]=1;
21     for(int i=0;i<=n;i++){
22         for(int j=0;j<=2;j++){
23             f[x][i][j]=inf;
24         }
25     }
26     f[x][1][0]=f[x][1][1]=f[x][1][2]=0;
27     for(int i=head[x];i;i=e[i].nxt){
28         if(e[i].to==fa) continue;
29         dfs(e[i].to,x);
30         for(int j=0;j<=sz[x]+sz[e[i].to];j++) tmp[j][0]=tmp[j][1]=tmp[j][2]=inf;
31         for(int j=0;j<=sz[x];j++){
32             for(int k=0;k<=sz[e[i].to];k++){
33                 updata(tmp[j+k][0],f[x][j][0]+f[e[i].to][k][0]+e[i].w*2);
34                 updata(tmp[j+k][1],f[x][j][1]+f[e[i].to][k][0]+e[i].w*2);
35                 updata(tmp[j+k][1],f[x][j][0]+f[e[i].to][k][1]+e[i].w);
36                 updata(tmp[j+k][2],f[x][j][0]+f[e[i].to][k][2]+e[i].w*2);
37                 updata(tmp[j+k][2],f[x][j][2]+f[e[i].to][k][0]+e[i].w*2);
38                 updata(tmp[j+k][2],f[x][j][1]+f[e[i].to][k][1]+e[i].w);
39             }
40         }
41         sz[x]+=sz[e[i].to];
42         for(int j=0;j<=sz[x];j++){
43             for(int k=0;k<=2;k++){
44                 f[x][j][k]=min(f[x][j][k],tmp[j][k]);
45             }
46         }
47     }
48     ans=min(ans,f[x][m][2]);
49 }
50 int main(){
51     scanf("%d%d",&n,&m);
52     for(int i=1;i<n;i++){
53         int x,y,z;
54         scanf("%d%d%d",&x,&y,&z);
55         add(x,y,z);
56         add(y,x,z);
57     }
58     ans=inf;
59     dfs(1,0);
60     printf("%d",ans);
61     return 0;
62 }

T3:Makik 的银行

Problem:

Makik 不仅是个完美主义者,更是一个慈善家。而支撑他做慈善的,是他在华尔街整条街排列一排的整齐的 n 个银行(完美主义)。每个银行的钱不一样多,有的有很多很多很多钱,但钱数都不会超过109;有的银行缺人打理,有很少很少很少很少很少钱,但钱数不会低于 1。
今天,Makik 本着慈善的目的,带来了有奖竞赛:他会告诉你其中 s 个银行所拥有的钱,同时给你 m条提示,每条提示给出三个整数 l, r, k,同时给出 k 个正整数。这 k 个数是 l 到 r 区间内的银行标号,表示从 l 到 r 这段区间的银行中,给出的这 k 个银行中任何一家所拥有的钱数目都多于 l 到 r 区间内其他银行的钱的数目。
请判断其是否有解,若有解则任意构造出一组满足条件的方案。

Solution:

很经典的一个题目,给定的都是类似 a>=b+c 的形式,所以我们可以很容易的想到查分约束然后跑最短路

a→b 权制为 c 的边为 a>=b+c

设𝑓[𝑥]为𝑥点可行的最小值,𝑎[𝑥]为𝑥位置已知的值,则𝑓[𝑥]=max(𝑓[𝑗]+𝑤(𝑗,𝑖),𝑎[𝑥]),其中𝑗有边连向𝑖。

通过拓扑排序+DP可以在𝑂(𝑛)时间内求出所有𝑓,如果存在环或者与题意不符则无解。

用线段树优化这个连边的过程,点数𝑂(𝑛+𝑚),边数𝑂(𝑘log𝑛)。

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=100010;
 4 const int maxm=400010;
 5 const int maxp=2000000;
 6 int n,s,m,i,x,y,z,tot,l[maxn<<1],r[maxn<<1],pos[maxn];
 7 int a[maxm],d[maxm],g[maxm],v[maxp],nxt[maxp],ed;
 8 char w[maxp];
 9 int h,t,q[maxm],f[maxm];
10 inline void add(int x,int y,char z){
11     d[y]++;
12     v[++ed]=y;
13     w[ed]=z;
14     nxt[ed]=g[x];
15     g[x]=ed;
16 }
17 inline int build(int a,int b){
18     int x=++tot;
19     if(a==b) return pos[a]=x;
20     int mid=(a+b)>>1;
21     add(l[x]=build(a,mid),x,0);
22     add(r[x]=build(mid+1,b),x,0);
23     return x;
24 }
25 inline void ask(int x,int a,int b,int c,int d){
26     if(c>d) return ;
27     if(c<=a&&b<=d){
28         add(x,tot,1);
29         return ;
30     }
31     int mid=(a+b)>>1;
32     if(c<=mid) ask(l[x],a,mid,c,d);
33     if(d>mid) ask(r[x],mid+1,b,c,d);
34 }
35 inline void up(int &a,int b){
36     if(a<b) a=b;
37 }
38 int main(){
39     scanf("%d%d%d",&n,&s,&m);
40     build(1,n);
41     while(s--){
42         scanf("%d%d",&x,&y);
43         a[pos[x]]=y;
44     }
45     while(m--){
46         scanf("%d%d%d",&x,&y,&z);
47         tot++;
48         for(i=1;i<=z;i++){
49             scanf("%d",&q[i]);
50             add(tot,pos[q[i]],0);
51         }
52         ask(1,1,n,x,q[1]-1);
53         ask(1,1,n,q[z]+1,y);
54         for(i=1;i<z;i++) ask(1,1,n,q[i]+1,q[i+1]-1);
55     }
56     for(h=i=1;i<=tot;i++){
57         if(!d[i]) f[q[++t]=i]=1;
58     }
59     while(h<=t){
60         x=q[h++];
61         if(f[x]>1000000000){
62             puts("NIE");
63             return 0;
64         }
65         if(a[x]){
66             if(a[x]<f[x]){
67                 puts("NIE");
68                 return 0;
69             }
70             if(a[x]>f[x]) f[x]=a[x];
71         }
72         for(i=g[x];i;i=nxt[i]){
73             up(f[v[i]],f[x]+w[i]);
74             if(!(--d[v[i]])) q[++t]=v[i];
75         }
76     }
77     if(t<tot){
78         puts("NIE");
79         return 0;
80     }
81     puts("TAK");
82     for(i=1;i<=n;i++) printf("%d ",f[pos[i]]);
83     return 0;
84 }
posted @ 2021-10-14 21:05  B_lank  阅读(34)  评论(0)    收藏  举报