1:懂的都懂(DFS)
众所周知,在互联网上有很多话是不好直接说出来的,不过一些模糊的图片仍然能让网友看懂你在说什么。然而对这种言论依然一定要出重拳,所以请你实现一个简单的匹配算法。
现在我们采集了原图的一些特征数据,由 N 个小于 255 的非负整数组成,假设对于给定的若干张由 M**i 个同样小于 255 的非负整数组成的新图的特征数据,每个数据都可以由原图中任意四个不同数据的平均值计算而来,则称新图为原图的相似图片。对于给出的数据,请你判断是不是相似图片。
注意,不同数据指的并非是数据的值不同,而是不能取同一个数据多次。对于两个相同值的数据,如果给出两次,则可以取两次。
输入格式
输入第一行是两个整数 N,K (1 ≤ N ≤ 50, 1 ≤ K ≤ 200),表示采集的原图的特征数据个数和新图的张数。 接下来一行为 N 个小于 255 的非负整数,表示原图的特征数据。 最后的 K 行,每行第一个数是 Mi (1 ≤ Mi ≤ 200),表示新图的特征数据个数。然后是 Mi 个小于 255 的非负整数,表示新图的特征数据。
输出格式
对于每一张新图,如果为相似图片,则在一行中输出 Yes,否则输出 No。
输入样例
5 3
4 8 12 20 40
3 11 16 19
3 12 16 19
10 11 11 11 11 11 11 11 11 11 11
输出样例
Yes
No
Yes
解法
使用DFS枚举每一种可能平均数,使用set去重并存储。(也可以用四重循环枚举)
最后,对每次输入的值进行判断。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e4;
int n,k;
int a[MAX];
set<int > st;
void getSet(int sum, int dep, int cnt){
if(cnt == 4){
st.insert(sum);
return;
}
if(dep == n || cnt > 4) return;
getSet(sum + a[dep],dep+1,cnt+1);
getSet(sum,dep+1,cnt);
}
int main(void){
cin>>n>>k;
for(int i = 0 ; i < n ; i ++){
cin>>a[i];
}
getSet(0,0,0);
while(k--){
int m;cin>>m;
string ans = "Yes";
for(int i = 0 ; i < m ; i ++){
int tep;cin>>tep;
if(st.count(tep*4) == 0) ans = "No";
}
cout<<ans<<endl;
}
return 0;
}
2:芬兰木棋(几何)
芬兰木棋(Mölkky,又称芬兰木柱)是源自芬兰的一项运动。哲哲将这个运动改造成了赛博朋克单人版,现在场上一开始有 N 根立起的小木棋(上面分别标有一个非负整数),哲哲投掷一根大木棋去击倒这些小木棋以获得分数。分数规则如下:
- 如果仅击倒 1 根木棋,则得木棋上的分数。
- 如果击倒 2 根或以上的木棋,则只得击倒根数的分数。(例如击倒 5 根,则得 5 分。)
哲哲固定站在 (0,0) 点上,四周放着若干个小木棋 (X**i,Y**i),坐标均为整数。每次哲哲可以朝一个方向扔出大木棋,大木棋会打倒这个方向上离哲哲最近的 k 个小木棋。哲哲游戏水平很高超,所以这个 k 可以自由控制。
请问哲哲最多能拿多少分,在获得最多分数的情况下最少需要扔出多少次大木棋?
规则与真实规则有较大出入,真实游玩时请以国际莫尔基组织的规则为准
输入格式
输入第一行是一个正整数 N (1 ≤ N ≤ 105),表示场上一开始有 N 个木棋。
接下来 N 行,每行 3 个整数 X**i,Y**i,P**i,分别表示木棋放置在 (X**i,Y**i),木棋上的分数是 P**i。坐标在 32 位整数范围内,分数为小于等于 1000 的正整数。
保证 (0,0) 点没有木棋,也没有木棋重叠放置。
输出格式
输出一行两个数,表示最多分数以及获得最多分数最少需要投掷大木棋多少次。
输入样例
11
1 2 2
2 4 3
3 6 4
-1 2 2
-2 4 3
-3 6 4
-1 -2 1
-2 -4 1
-3 -6 1
-4 -8 2
2 -1 999
输出样例
1022 9
解法
对于每个点,用结构体存储信息。
计算(0,0)- (a, b)的斜率k,使用map映射到某个集合上。
每个集合代表一条直线。
对同一条直线上的每个点,进行遍历计算:
对于1的序列,一次全部打倒最优;对于其他数字,逐个击倒最优。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e6;
int n,idx,sum,cnt;
struct Point{
int x,y,w;
Point(int x_,int y_,int w_){
x = x_;
y = y_;
w = w_;
}
bool operator<(const Point & rhs){
if(x != rhs.x) return x < rhs.x;
else return y < rhs.y;
}
};
vector<Point> v[MAX];
unordered_map<double,int > mp;
int main(void){
cin>>n;
idx = 1;
for(int i = 0 ; i < n ; i ++){
int x,y,w;
cin>>x>>y>>w;
if(x == 0){
v[0].push_back(Point(x,y,w));
continue;
}
double k = (1.0*y)/x;
if(!mp[k]) mp[k] = idx++;
v[mp[k]].push_back(Point(x,y,w));
}
for(int i = 0 ; i < idx ; i ++){
sort(v[i].begin(),v[i].end());
int last = 0;
for(Point it : v[i]){
sum += it.w;
if(it.w == last && it.w == 1) continue;
else cnt++;
last = it.w;
}
}
cout<<sum<<" "<<cnt<<endl;
}
3:打怪升级(Floyd+Dijkstra)
很多游戏都有打怪升级的环节,玩家需要打败一系列怪兽去赢取成就和徽章。这里我们考虑一种简单的打怪升级游戏,游戏规则是,给定有 N 个堡垒的地图,堡垒之间有道路相连,每条道路上有一只怪兽把守。怪兽本身有能量,手里的武器有价值。打败怪兽需要的能量等于怪兽本身的能量,而怪兽一旦被打败,武器就归玩家所有 —— 当然缴获的武器价值越高,玩家就越开心。
你的任务有两件:
-
- 帮助玩家确定一个最合算的空降位置,即空降到地图中的某个堡垒,使得玩家从这个空降点出发,到攻下最难攻克(即耗费能量最多)的那个堡垒所需要的能量最小;
-
- 从这个空降点出发,帮助玩家找到攻克任意一个其想要攻克的堡垒的最省能量的路径。如果这种路径不唯一,则选择沿途缴获武器总价值最高的解,题目保证这种解是唯一的。
输入格式
输入第一行给出两个正整数 N (≤1000) 和 M,其中 N 是堡垒总数,M 是怪兽总数。为简单起见,我们将堡垒从 1 到 N 编号。随后 M 行,第 i 行给出了第 i 只怪兽的信息,格式如下:
B1 B2 怪兽能量 武器价值
其中 B1
和 B2
是怪兽把守的道路两端的堡垒编号。题目保证每对堡垒之间只有一只怪兽把守,并且 怪兽能量
和 武器价值
都是不超过 100 的正整数。
再后面是一个正整数 K(≤N)和玩家想要攻克的 K 个目标堡垒的编号。
输出格式
首先在一行中输出玩空降的堡垒编号 B0
。如果有多种可能,则输出编号最小的那个。
随后依次为玩家想要攻克的每个堡垒 B
推荐最省能量的攻克路径,并列出需要耗费的能量值和沿途缴获武器的总价值。注意如果最省力的路径不唯一,则选择沿途缴获武器总价值最高的解。格式为:
B0->途经堡垒1->...->B
总耗费能量 武器总价值
输入样例:
6 12
1 2 10 5
2 3 16 20
3 1 4 2
2 4 20 22
4 5 2 2
5 3 12 6
4 6 8 5
6 5 10 5
6 1 20 25
1 5 8 5
2 5 2 1
2 6 8 5
4
2 3 6 5
输出样例
5
5->2
2 1
5->1->3
12 7
5->4->6
10 7
5
0 0
解法
使用Floyd找到起点,根据该起点,使用双权值的Dijkstra求"最短路",并记录路径。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e3 + 50;
const int INF = 0x3f3f3f3f;
struct Edge{
int d;
int w;
Edge(){
d = INF;
w = -INF;
}
Edge(int d_,int w_){
d = d_;
w = w_;
}
};
int n,m,k,s;
Edge edge[MAX][MAX];
int dist[MAX],val[MAX],path[MAX];
int flEdge[MAX][MAX];
bool visited[MAX];
int floyd(){
for(int i = 1 ; i <= n ; i ++){
for(int j = 1 ; j <= n ; j ++){
flEdge[i][j] = edge[i][j].d;
}
}
for(int t = 1 ; t <= n ; t ++){
for(int i = 1 ; i <= n ; i ++){
if(flEdge[i][t] == INF) continue;
for(int j = 1 ; j <= n ; j ++){
flEdge[i][j] = min(flEdge[i][j],flEdge[i][t] + flEdge[t][j]);
}
}
}
int res = 1;
int min_ = INF;
for(int i = 1 ; i <= n ; i ++){
int max_ = -1;
for(int j = 1 ; j <= n ; j ++){
if(j != i) max_ = max(max_,flEdge[i][j]);
}
if(max_ < min_){
min_ = max_;
res = i;
}
}
return res;
}
void dijkstra(){
visited[s] = true;
for(int i = 1 ; i <= n ; i ++){
dist[i] = edge[s][i].d;
val[i] = edge[s][i].w;
path[i] = s;
}
for(int i = 2 ; i <= n ;i ++){
int now = -1;
int min_ = INF;
for(int j = 1 ; j <= n ; j ++){
if(!visited[j] && dist[j] < min_){
min_ = dist[j];
now = j;
}
}
visited[now] = true;
for(int j = 1 ; j <= n ; j ++){
if(!visited[j] && dist[j] > dist[now] + edge[now][j].d){
dist[j] = dist[now] + edge[now][j].d;
val[j] = val[now] + edge[now][j].w;
path[j] = now;
}else if(!visited[j] && dist[j] == dist[now] + edge[now][j].d && val[j] < val[now] + edge[now][j].w){
dist[j] = dist[now] + edge[now][j].d;
val[j] = val[now] + edge[now][j].w;
path[j] = now;
}
}
}
}
int main(void){
cin>>n>>m;
for(int i = 1 ; i <= m ; i ++){
int x,y,d,w;
cin>>x>>y>>d>>w;
edge[x][y] = Edge(d,w);
edge[y][x] = Edge(d,w);
}
s = floyd();
cout<<s<<endl;
dijkstra();
cin>>k;
while(k--){
int to;cin>>to;
int tep = to;
stack<int > st;
while(tep != s){
st.push(tep);
tep = path[tep];
}st.push(s);
while(!st.empty()){
cout<<st.top();
if(st.top() != to) cout<<"->";
st.pop();
}cout<<endl;
int res1 = dist[to] == INF?0:dist[to];
int res2 = val[to] == -INF?0:val[to];
cout<<res1<<" "<<res2<<endl;
}
return 0;
}
4:疫情防控(并查集)
疫情尚未结束,严防疫情反复。为了做好疫情防控工作,国内设置了地区风险等级,对于中高风险地区的人员采取限制移动、居家隔离等手段。
为了研究疫情防控对于跨地区交通运输的影响,假设现在有 N 个机场,M 条航线,每天都会新增一个防控地区,一个防控地区会导致一个机场无法正常运作,航线也自然无法正常运行,每天会有 Q**i 对旅客从 X**i 机场前往 Y**i 机场,请计算有多少对旅客会受到影响无法完成行程。
旅客只要能直达或通过若干次中转,且乘坐的所有航线的出发和到达机场都正常运作,即视作可完成行程。
输入格式
输入第一行是三个整数 N,M,D (1≤N≤5×104, 1≤M≤2×105, 1≤D≤103), 表示机场数、航线数以及新增防控地区的天数。
接下来首先有 M 行,每行给出空格分隔的两个数字 A 和 B,表示编号为 A 和 B 的机场之间有一条航线。航线是双向的,机场编号从 1 到 N。
然后是 D 块输入,每块输入内第一行为空格分隔的两个整数 C 和 Q (1≤Q≤103),表示新增机场编号为 C 所在的城市为防控地区,今天有 Q 段行程。数据保证新增的城市之前一定不是防控地区。
接下来的 Q 行,每行是空格分隔的两个数字 X 和 Y,表示编号为 X 和 Y 的机场的一段行程。行程有可能包括之前就已经成为防控地区的城市。
输出格式
对于每天的询问,请在一行中输出在新增了一个防控地区后当天的行程有多少不能成行。
输入样例
5 5 3
1 2
1 3
1 5
2 5
3 4
4 3
1 3
1 4
2 3
5 3
3 4
2 3
3 5
1 3
2 3
2 5
3 4
输出样例
1
2
3
解法
本质是:需要多次判断两点是否连通,在这个过程中,逐步从图中删除点,维护连通性。
考虑使用并查集,并查集在查找过程中会优化路径,进行删除操作后无法保证图联通的正确性。
但是,加入新边的操作能够保证连通性正确。
因此,可以在创建图时只保留始终未被删除的点,并将每次的操作存储。
从后往前遍历每次操作,查询连通性并统计,并不断恢复被删除的点。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAX = 5e4 + 100;
int n,m,d;
int pre[MAX],back[MAX];
bool deleted[MAX];
stack<int > ans;
vector<int > e[MAX];
vector<pair<int,int> > querys[MAX];
void initSet(){
for(int i = 1 ; i <= n ; i ++){
pre[i] = i;
}
}
int find(int x){
if(pre[x] == x) return x;
else return pre[x] = find(pre[x]);
}
void Union(int x,int y){
int fx = find(x);
int fy = find(y);
if(fx != fy) pre[fx] = fy;
}
int main(void){
cin>>n>>m>>d;
initSet();
for(int i = 0 ; i < m ; i ++){
int x,y;cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
for(int i = 0 ; i < d ; i ++){
int cnt;
cin>>back[i]>>cnt;
deleted[back[i]] = true;
while(cnt--){
int a,b;cin>>a>>b;
querys[i].push_back(make_pair(a,b));
}
}
for(int i = 1 ; i <= n ; i ++){
if(deleted[i]) continue;
for(auto to : e[i]){
if(deleted[to]) continue;
Union(i,to);
}
}
for(int i = d-1 ; i >= 0 ; i--){
int res = 0;
for(pair<int,int> q : querys[i]){
int x = q.first;
int y = q.second;
if(find(x) != find(y)) res++;
}
ans.push(res);
for(auto to : e[back[i]]){
if(deleted[to]) continue;
Union(back[i],to);
}
deleted[back[i]] = false;
}
while(!ans.empty()){
cout<<ans.top()<<endl;
ans.pop();
}
return 0;
}