文章目录
- T1:图
- solution
- code
- T2:劫富济贫
- solution
- code
- T3:小A的树
- solution
- code
- T4:游戏
- solution
- code
T1:图
【问题描述】
给你一个n个点,m条边的无向图,每个点有一个非负的权值ci,现在你需要选择一些点,使得每一个点都满足:
如果这个点没有被选择,则与它有边相连的所有点都必须被选择。
问:满足上述条件的点集中,所有选择的点的权值和最小是多少?
QYQ很快就解决了这个问题,但是他已经回到了左下角……没有留下答案,现在只好请你来解决这个问题啦!
【输入描述】
从文件graph.in中输入数据。
输入的第一行包含两个整数n,m
输入的第二行包含n个整数,其中第i个整数代表ci
输入的第三行到第m+2行,每行包含两个整数u,v,代表点u和点v之间有一条边
【输出描述】
输出到文件graph.out中。
输出的第一行包含一个整数,代表最小的权值和
【样例】
Sample Input
3 1
1 2 3
3 1
Sample Output
1
【样例解释】
只选择1号点,满足题意
【数据规模】
对于20% 的数据:n<=10
对于40%的数据:n<=20
对于100%的数据:1<=n<=50, 1<=m<=500, 0<=c<=1000
图中可能会有重边,自环。
点的编号为1—n。
solution
直接大法师 搜索即可
当然不是指纯O(2n)O(2^n)O(2n)那么头铁的暴搜
其实也差不多啦~
可以加入两个强劲剪枝
①:当前搜索的值已经大于了已经找到的最小值,就没有搜下去的意义了
②:当uuu点强制不选后,所有与uuu点直接相连的点就必须选,这也可以剪掉很多情况
最后就发现跑的贼nm快,但是就很玄学,懂?——算不了时间复杂度
直接帮你从O(250)O(2^{50})O(250)TTT飞直接剪进ACACAC
这道题告诉我,有的时候正解真的就是普普通通的搜索,只不过平时因为都是思维方面的训练
所以搜索一般是用来想不出正解搞简单的部分分用的算法
搜索——正解算法思考下优先级可能没那么高,但是当什么算法都不行的时候,可能就要尝试一下了
而且一般很小的范围,除掉很巧妙的结论或者技巧题——贪心,状压,搜索就很大概率是正解
因为搜索的时间复杂度加上剪枝后就变得很玄学
所以考场上,就算知道暴搜点强制选了后,与之直接相连的点必须不选的情况下
我也没敢直接就大法师冲,平时就是直接刚了,莫名这个时候稳如一匹翻车的老狗
code
#include <cstdio>
#include <vector>
using namespace std;
#define maxn 55
#define inf 0x3f3f3f3f
vector < int > G[maxn];
int n, m, ans, minn, cnt;
int c[maxn], p[maxn], tot[maxn];
bool vis[maxn], flag[maxn];
int g[maxn][maxn];void dfs1( int u, int fa ) {vis[u] = 1, p[++ cnt] = u;for( int i = 0;i < G[u].size();i ++ )if( vis[G[u][i]] ) continue;else dfs1( G[u][i], u );
}void dfs2( int u, int cost ) {if( cost > minn ) return;if( u > cnt ) {minn = min( minn, cost );return;}dfs2( u + 1, cost + c[p[u]] );if( ! flag[p[u]] && ! tot[p[u]] ) {for( int i = 0;i < G[p[u]].size();i ++ )tot[G[p[u]][i]] ++;dfs2( u + 1, cost );for( int i = 0;i < G[p[u]].size();i ++ )tot[G[p[u]][i]] --;}
}int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ )scanf( "%d", &c[i] );for( int i = 1, u, v;i <= m;i ++ ) {scanf( "%d %d", &u, &v );g[u][v] = g[v][u] = 1;if( u == v ) flag[u] = 1; }for( int i = 1;i <= n;i ++ )for( int j = 1;j <= n;j ++ )if( g[i][j] ) G[i].push_back( j );for( int i = 1;i <= n;i ++ )if( ! vis[i] ) {cnt = 0, minn = inf;dfs1( i, 0 );dfs2( 1, 0 );ans += minn;}printf( "%d\n", ans );return 0;
}
T2:劫富济贫
【问题描述】
吕弗·普自小从英国长大,受到骑士精神的影响,吕弗·普的梦想便是成为一位劫富济贫的骑士。
吕弗·普拿到了一份全国富豪的名单(不在名单上的都是穷人),上面写着所有富豪的名字以及他们的总资产,比如豪特斯·珀去年资产有86E,吕弗·普就会准备抢来资助贫困的伯恩兄弟……
现在吕弗·普做了M次打劫计划,每次要打劫若干个人,他想知道每次能打劫到的总资产是多少
【输入格式】
第一行一个正整数N,代表富豪的个数
接下来N行,每行一个由小写字母组成的字符串Si和一个非负整数Wi,分别代表第i个富豪的名字和第i个富豪的资产数量
然后一个正整数M,代表吕弗·普的打劫次数
接下来M行,每行第一个数为正整数Xi,代表这次要打劫Xi个人,接下来有X个字符串,说明了这Xi个人是谁
【输出格式】
对于每次打劫任务,输出一行一个整数表示打劫到的总资产
如果这次打劫任务中打劫了一个穷人,那就输出-1
【样例输入】
2
a 10
b 20
3
2 a b
1 b
2 a c
【样例输出】
30
20
-1
【数据范围与约定】
对于30% 的数据,输入中每个名字的长度均为1
对于60% 的数据,N,∑Xi<= 100,输入中每个名字的长度<=10
对于100%的数据,N,∑Xi<= 10^5 ,输入中所有名字的总长<=2*10^6 ,Wi<=10^9,保证任意两个富豪名字不同,但不保证打劫计划中会不会有重复的人(重复的人会被重复打劫)
solution
法一:
直接mapmapmap就跑,应该是只能拿到70%70\%70%,但是我旁边的香香mm就莫名卡过去了,我就没卡过去
明知字典树可做的情况下还是以为3s3s3s,再怎么说mapmapmap也绰绰有余的
法二:hashhashhash应该是能AAA的
法三:正解——trietrietrie字典树
还是很裸的了,大部分人在一看到这句话肯定就知道怎么写了
这里提一句关于数组大小的问题——此题只给了256MB
也就是说只允许一个trie[2e6][26]trie[2e6][26]trie[2e6][26]大小的数组再加点其他小数组
还必须留有一定的运行空间
刚开始我的flagflagflag数组开得跟trietrietrie一样大,然后就自然地MLEMLEMLE
后面才领悟到——其实只用开nnn大小的就可以了
因为虽然字典树是每个节点都有262626个儿子,但是我总点数就等于总名字长度,不会超过2e62e62e6
code
#include <cstdio>
#include <cstring>
#define maxm 2000005
#define maxn 500005
int n, m, cnt;
int trie[maxm][26], w[maxn];
char s[maxm];
int flag[maxm];void insert( char *s, int id ) {int p = 0, len = strlen( s );for( int i = 0;i < len;i ++ ) {int c = s[i] - 'a';if( ! trie[p][c] ) trie[p][c] = ++ cnt;p = trie[p][c];}flag[p] = id;
}int query( char *s ) {int p = 0, len = strlen( s );for( int i = 0;i < len;i ++ ) {int c = s[i] - 'a';if( ! trie[p][c] ) return 0;p = trie[p][c];}return flag[p];
}int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%s %d", s, &w[i] );insert( s, i );}scanf( "%d", &m );for( int i = 1, x;i <= m;i ++ ) {scanf( "%d", &x );long long ans = 0;bool no = 0;for( int j = 1;j <= x;j ++ ) {scanf( "%s", s );int id = query( s );if( id ) ans += w[id];else no = 1;}if( no ) printf( "-1\n" );else printf( "%lld\n", ans );}return 0;
}
T3:小A的树
【问题描述】
给出一棵n个点的树,每个点有黑白两种颜色。q次询问,每次询问给出x和y,问能否选出一个x个点的联通子图,使得其中黑点数目为y。
【输入描述】
第一行一个正整数 T 表示数据组数。
对于每一组数据,第一行有两个用空格隔开的正整数,分别是 n 和 q ,表示树的节点数和询问次数。
接下来 n-1 行,每行两个用空格隔开的正整数和,表示和间有一条边相连。
接下来一行有 n 个用空格隔开的整数,其中值若0,则表示第 i 个点为白色,否则为黑色。
接下来 q 行,每行两个用空格隔开的整数 x 和 y 。
T=1,n<=5000,q<=10^5
【输出描述】
对于每一组数据,输出 q 行,每行为 “YES” 或者 “NO” (不含双引号),表示对于给定的和,能否满足小A 的要求。
每相邻两组数据的输出之间空一行。
【样例输入】
1
9 4
4 1
1 5
1 2
3 2
3 6
6 7
6 8
9 6
0 1 0 1 0 0 1 0 1
3 2
7 3
4 0
9 5
【样例输出】
YES
YES
NO
NO
solution
T=1T=1T=1就挺离谱儿的
这道题如果发现了一个性质👇就很简单了 然并卵,我既没有发现也不觉得发现后就简单了
对于某一大小的连通子图,其包含黑点数的最小值与最大值之间的所有点数目都能够取得到
简单感性证明一下哈哈哈哈
一个连通子图可以删除一个点再加入一个点,保证连通子图的大小依然不变
且此时黑点的数目变化最多只为111
然后此题就摇身一变为简单的树形dp带背包
如果对树形dp+背包不熟悉的可以先去做一下洛谷的选课
定义f[i][j]f[i][j]f[i][j]表示:iii子树中选jjj个点(即大小为jjj的连通子图)中包含黑点个数的最大值
定义g[i][j]g[i][j]g[i][j]表示:iii子树中选jjj个点(即大小为jjj的连通子图)中包含黑点个数的最小值
注意树形背包的正确枚举:只使用已经遍历过的点数目和当前子树中的点数目转移
否则会被链卡到 O(n3)O(n^3)O(n3)
code
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define maxn 5005
vector < int > G[maxn];
int T, n, Q;
int w[maxn], siz[maxn];
int f[maxn][maxn], g[maxn][maxn];void dfs( int u, int fa ) {siz[u] = 1;f[u][1] = g[u][1] = w[u];for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa ) continue;dfs( v, u );for( int j = siz[u];j;j -- )for( int k = siz[v];k;k -- ) {f[u][j + k] = max( f[u][j + k], f[v][k] + f[u][j] );g[u][j + k] = min( g[u][j + k], g[v][k] + g[u][j] );}siz[u] += siz[v];}for( int i = 1;i <= n;i ++ ) {f[0][i] = max( f[0][i], f[u][i] );g[0][i] = min( g[0][i], g[u][i] );}
}int main() {scanf( "%d", &T );while( T -- ) {scanf( "%d %d", &n, &Q );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u );}for( int i = 1;i <= n;i ++ )scanf( "%d", &w[i] );memset( g, 0x3f, sizeof( g ) );dfs( 1, 0 );for( int i = 1, x, y;i <= Q;i ++ ) {scanf( "%d %d", &x, &y );if( g[0][x] <= y && y <= f[0][x] ) printf( "YES\n" );else printf( "NO\n" );}}return 0;
}
T4:游戏
【问题描述】
WYF从小就爱乱顶,但是顶是会造成位移的。他之前水平有限,每次只能顶出k的位移,也就是从一个整点顶到另一个整点上。我们现在将之简化到数轴上,即从 一个整点可以顶到与自己相隔在k之内的数轴上的整点上。现在WYF的头变多了,于是他能顶到更远的地方,他能顶到任意整点上。现在他在玩一个游戏,这个游 戏里他只能向正方向顶,同时如果他从i顶到j,他将得到a[j] * (j - i)的分数,其中a[j]是j点上的分数,且要求j > i, 他最后必须停在n上。
现给出1~n上的所有分数,原点没有分数。他现在在原点,没有分。WYF想知道他最多能得多少分。
【输入描述】
第一行一个整数n。
第二行有n个整数,其中第i个数表示a[j]。
【输出描述】
一个整数,表示WYF最多能得到的分数。
【样例输入】
3
1 1 50
【样例输出】
150
【数据范围】
对于60%的数据,n<=1000;
对于100%的数据,n<=100000,0<=a[j]<=50
solution
本场最简单的题没有之一——因为我A了
法一:我的做法——贪心
在[1,n][1,n][1,n]间,最大值的地方假设为iii,我肯定直接从000飞到iii上面,中间不会有中转点
然后就从iii再继续飞到[i+1,n][i+1,n][i+1,n] 中最大值的地方,以此类推,实现方法很多,不赘述
法二:斜率优化
考场上我也推出来了,因为这个样子长得就在暗示我用斜率优化搞他
但是我发现贪心不更简单吗,所以我就没敲
f[i]=max(f[i],f[j]+a[i]∗(i−j))f[i]=max(f[i],f[j]+a[i]*(i-j))f[i]=max(f[i],f[j]+a[i]∗(i−j))
假设j<k<ij<k<ij<k<i且选择jjj优于选择kkk,翻译成C++👇
f[j]+a[i]∗(i−j)>f[k]+a[i]∗(i−k)f[j]+a[i]*(i-j)>f[k]+a[i]*(i-k)f[j]+a[i]∗(i−j)>f[k]+a[i]∗(i−k)
f[j]−f[k]>a[i]∗(j−k)f[j]-f[k]>a[i]*(j-k)f[j]−f[k]>a[i]∗(j−k) 注意符号变化哦~
(f[j]−f[k])/(j−k)<a[i](f[j]-f[k])/(j-k)<a[i](f[j]−f[k])/(j−k)<a[i]
法三:我觉得特别巧妙——香香mm的做法
她的心路历程:看这个aaa范围这么小,在暗示她时间复杂度跟aaa挂钩,那肯定是O(n∗a)O(n*a)O(n∗a)了
考虑从式子本身进行变形
f[i]=f[j]+a[i]∗(i−j)=f[j]−j∗a[i]+a[i]∗if[i]=f[j]+a[i]*(i-j)=f[j]-j*a[i]+a[i]*if[i]=f[j]+a[i]∗(i−j)=f[j]−j∗a[i]+a[i]∗i
发现a[i]∗ia[i]*ia[i]∗i与jjj无关,所以就考虑求f[j]−j∗a[i]f[j]-j*a[i]f[j]−j∗a[i]的最大值
但是因为j<ij<ij<i,所以在jjj的时候是无法知道a[i]a[i]a[i]的值的
那么就直接硬刚
是男人就硬刚
暴力枚举a[i]a[i]a[i]的大小
去求出当a[i]=xa[i]=xa[i]=x的时候,f[j]−j∗a[i]f[j]-j*a[i]f[j]−j∗a[i]的最大值究竟是多少
存下来就可以了,我们不想知道是谁转移的,我们只想要转移后的最大值
for( int i = 1;i <= n;i ++ ) {scanf( "%d", &a );ans = max( ans, pre[a] + a * i );for( int j = 1;j <= 50;j ++ )pre[a] = max( pre[a], ans - i * j );
}
printf( "%d", ans );
code
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100005
struct node {int v, id;node() {}node( int V, int Id ) {v = V, id = Id;}
}b[maxn];
int n;
int a[maxn];bool cmp( node x, node y ) {return x.v > y.v;
}
int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%d", &a[i] );for( int i = 1;i <= n;i ++ )b[i] = node( a[i], i );sort( b + 1, b + n + 1, cmp );int p = 0, ans = 0;for( int i = 1;i <= n;i ++ ) {int val = b[i].v, ip = b[i].id;if( ip < p ) continue;else ans += val * ( ip - p ), p = ip;}printf( "%d\n", ans );return 0;
}