测验- 掌握魔法の东东 II
从瑞神家打牌回来后,东东痛定思痛,决定苦练牌技,终成赌神!
东东有 A × B 张扑克牌。每张扑克牌有一个大小(整数,记为a,范围区间是 0 到 A - 1)和一个花色(整数,记为b,范围区间是 0 到 B - 1。
扑克牌是互异的,也就是独一无二的,也就是说没有两张牌大小和花色都相同。
“一手牌”的意思是你手里有5张不同的牌,这 5 张牌没有谁在前谁在后的顺序之分,它们可以形成一个牌型。 我们定义了 9 种牌型,如下是 9 种牌型的规则,我们用“低序号优先”来匹配牌型,即这“一手牌”从上到下满足的第一个牌型规则就是它的“牌型编号”(一个整数,属于1到9):
同花顺: 同时满足规则 2 和规则 3.
顺子 : 5张牌的大小形如 x, x + 1, x + 2, x + 3, x + 4
同花 : 5张牌都是相同花色的.
炸弹 : 5张牌其中有4张牌的大小相等.
三带二 : 5张牌其中有3张牌的大小相等,且另外2张牌的大小也相等.
两对: 5张牌其中有2张牌的大小相等,且另外3张牌中2张牌的大小相等.
三条: 5张牌其中有3张牌的大小相等.
一对: 5张牌其中有2张牌的大小相等.
要不起: 这手牌不满足上述的牌型中任意一个.
现在, 东东从A × B 张扑克牌中拿走了 2 张牌!分别是 (a1, b1) 和 (a2, b2). (其中a表示大小,b表示花色)
现在要从剩下的扑克牌中再随机拿出 3 张!组成一手牌!!
其实东东除了会打代码,他业余还是一个魔法师,现在他要预言他的未来的可能性,即他将拿到的“一手牌”的可能性,我们用一个“牌型编号(一个整数,属于1到9)”来表示这手牌的牌型,那么他的未来有 9 种可能,但每种可能的方案数不一样。
现在,东东的阿戈摩托之眼没了,你需要帮他算一算 9 种牌型中,每种牌型的方案数。
Input
第 1 行包含了整数 A 和 B (5 ≤ A ≤ 25, 1 ≤ B ≤ 4).
第 2 行包含了整数 a1, b1, a2, b2 (0 ≤ a1, a2 ≤ A - 1, 0 ≤ b1, b2 ≤ B - 1, (a1, b1) ≠ (a2, b2)).
Output
输出一行,这行有 9 个整数,每个整数代表了 9 种牌型的方案数(按牌型编号从小到大的顺序)
思路:
这道题可以考虑顺序的优先性,这样就可以暴力做出来。因为A最大为25,B最大为4.最多也就100张牌,这时候O(n^3)复杂度应该能够满足最大的情况,首先是同花顺是优先级最高的,满足五张牌同花并且顺序即可,其次就是顺子和同花,顺子的情况都满足的话,如果所有牌不相等,那就是要不起。找到满足要不起的情况后判断炸弹、三带中三带二和三条的情况,接着判断两对和一对。有了优先级判断,选择vector存储方式,将(a1,b1)(a2,b2)塞入手牌,其余塞入牌堆。然后利用三次循环遍历将牌堆中选取三张塞入手牌然后将所有手牌按照a从小到大排列(大小相同按花色b)并进行判断,然后执行判断后再删除该手牌,最后统计出所有牌型的方案数即可。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define _for(i,a,b) for(int i=a;i<b;i++)
struct card {
int a, b;//牌号和花色
card(int a1, int b1)
{
a = a1;
b = b1;
}
};
bool cmp(const card& c1, const card& c2)
{
if (c1.a != c2.a)
return c1.a < c2.a;
else return c1.b < c2.b;
}
vector<card>v1; //代表手牌
vector<card>v2; //代表牌堆
int A, B, a1, a2, b1, b2;
int ans[10] ;
int x[5], y[5];
void init()//初始化将(a1,b1)(a2,b2)塞进手牌,其余塞进牌堆
{
v1.clear();
v2.clear();
_for(i, 0, A)
{
_for(j, 0, B)
{
if (((i == a1) && (j == b1) || (i == a2) && (j == b2)))
v1.push_back(card(i,j));
else v2.push_back(card(i, j));
}
}
}
void judge(vector<card> v)
{
sort(v.begin(), v.end(),cmp);
_for(i, 0, 5)
{
x[i] = v[i].a;
y[i] = v[i].b;
}
bool j1 = false, j2 = false;
if (x[1] == x[0] + 1 && x[2] == x[0] + 2 && x[3] == x[0] + 3 && x[4] == x[0] + 4)
j1 = true;
if (y[0] == y[1] &&y[1] == y[2] &&y[2] == y[3] && y[3] == y[4])
j2 = true;
if (j1 && j2) //同花顺
{
ans[1]++;
return;
}
if (j1) { ans[2]++; return; }//顺子
if (j2) { ans[3]++; return; }//同花
if (x[0] != x[1] && x[1] != x[2] && x[2] != x[3] && x[3] != x[4]) //要不起
{
ans[9]++;
return;
}
if ((x[0] == x[1] && x[1] == x[2] && x[2] == x[3]) || (x[1] ==x[2] && x[2] == x[3] && x[3] == x[4]))//炸弹
{
ans[4]++;
return;
}
if ((x[0] == x[1] && x[1] == x[2]))//三带二
{
if (x[3] == x[4])
{
ans[5]++;
return;
}
else //三条
{
ans[7]++;
return;
}
}
if ((x[2] == x[3] && x[3] == x[4]))
{
if (x[0] == x[1])
{
ans[5]++;
return; }//三带二
else //三条
{
ans[7]++;
return;
}
}
if ((x[1] ==x[2] && x[2] == x[3])) //三条
{
ans[7]++;
return;
}
if (x[0] == x[1])//两对
{
if (x[2] == x[3] || x[3] == x[4])
{
ans[6]++;
return;
}
else//一对
{
ans[8]++;
return;
}
}
else if (x[1] == x[2] && x[3] == x[4])
{
ans[6]++;
return;
}
else
{
ans[8]++;
return;
}
}
int main()
{
ios::sync_with_stdio(false);
memset(ans, 0, sizeof(ans));
memset(x, 0, sizeof(x));
memset(y, 0, sizeof(y));
cin >> A >> B;
cin >> a1 >> b1 >> a2 >> b2;
init();
_for(i, 0, A * B - 2)
{
v1.push_back(v2[i]);
_for(j, i + 1, A * B - 2)
{
v1.push_back(v2[j]);
_for(k, j + 1, A * B - 2)
{
v1.push_back(v2[k]);
judge(v1);
v1.pop_back();
}
v1.pop_back();
}
v1.pop_back();
}
_for(i, 1, 10)
cout << ans[i] << " ";
return 0;
}
A - 氪金带东
题意:
实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
提示: 样例输入对应这个图,从这个图中你可以看出,距离1号电脑最远的电脑是4号电脑,他们之间的距离是3。 4号电脑与5号电脑都是距离2号电脑最远的点,故其答案是2。5号电脑距离3号电脑最远,故对于3号电脑来说它的答案是3。同样的我们可以计算出4号电脑和5号电脑的答案是4.
Input
输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。
Output
对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N).
题意:
这道题是直接找某个节点所能到达的最长路径。首先假设两个端点a1,a2。a1 a2代表树的直径,而某一点所能到达的最长路径的终点必为a1,a2中一点。但是不知道最长路径是哪个方向,需要分别从a1,a2再次遍历共需三次遍历,这道题利用dfs,而dfs代码可以写一起,这样就可以使代码更加整洁。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
struct edge {
int u;
int v;
int w;
int next;
}e[100000];
int a[100000], b[100000], c[100000], count1, d;
bool vis[100000];
void adde(int u, int v, int w)
{
e[count1].u = u;
e[count1].v = v;
e[count1].w = w;
e[count1].next = a[u];
a[u] = count1;
count1++;
}
void dfs(int m, int n, int* c)
{
c[m] = n;
if (c[d] < c[m])
d = m;
for (int i = a[m]; i != -1; i = e[i].next)
{
if (!vis[e[i].v]) {
vis[e[i].v] = true;
dfs(e[i].v, n + e[i].w, c);
}
}
}
int main()
{
int N;
while (cin >> N)
{
count1 = 0;
for (int i = 0; i < N + 1; i++)
a[i] = -1;
for (int i = 2; i <= N; i++)
{
int v, w;
cin >> v >> w;
adde(i, v, w);
adde(v, i, w);
}
memset(vis, 0, sizeof(vis));
b[d] = 0;
vis[1] = 1;
dfs(1, 0, b);
memset(vis, 0, sizeof(vis));
b[d] = 0;
vis[d] = 1;
dfs(d, 0, b);
memset(vis, 0, sizeof(vis));
c[d] = 0;
vis[d] = 1;
dfs(d, 0, c);
for (int i = 1; i <= N; i++)
{
int maxd = max(b[i], c[i]);
cout << maxd << endl;
}
}
}
B - 戴好口罩!
题意:
新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
如果一个感染者走入一个群体,那么这个群体需要被隔离!
小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
危!!!
时间紧迫!!!!
需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。
众所周知,学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
请你编写程序解决!戴口罩!!
Input
多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
学生编号为0~n-1
小A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
Output
输出要隔离的人数,每组数据的答案输出占一行
思路:
如果群小A(0)在这个群体中,就需要进行隔离。也就可以利用并查集的方法:1.查询a,b是否在同一组.2.合并a和b所在的组。每个团体 都是一个树形结构,每个元素是一个结点,每团体是一个树。然后初始化n个结点,每个点就是一个集合,自己的父节点就是自己;查询每一个节点不断寻找自己的父节点,若此时自己的父节点就是自己,那么该点为集合的根结点,返回该点。合并有小A(0)的团体 即只需要合并两个集合的根结点,统计出团体的人数即可。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int pre[40000];
int rank1[40000];
int findfather(int i) //找爹
{
while (pre[i]!=i)
{
pre[i] = pre[pre[i]];
i = pre[i];
}
return i;
}
void unionfather(int a, int b) //认爹
{
a = findfather(a);
b = findfather(b);
if (a == b) return;
if (rank1[a] > rank1[b])
{
swap(a, b);
}
pre[a] = b;
rank1[b] = (rank1[a] +=rank1[b]);
}
int main()
{
int n, m;
int a, b, c;
ios::sync_with_stdio(false);
while (cin >> n >> m)
{
if (n == 0 && m == 0) break;
for (int i = 0; i < n; i++)
{
pre[i] = i;
rank1[i] = 1;
}
for (int i = 0; i < m; i++)
{
cin >> a >> b;
for (int j = 1; j < a; j++)
{
cin >> c;
unionfather(b, c);
}
}
cout << rank1[findfather(0)] << endl;
}
return 0;
}
C - 掌握魔法の东东 I
题意:
东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌水。
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)
东东为所有的田灌氵的最小消耗
Input
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
Output
东东最小消耗的MP值。
思路:
这是一个最小生成树问题,传送门可以用边权的方式来替代,可以将技能:黄河之水天上来通过新引入一个顶点,然后将消耗的mp建立新的边权。这时候就是一个完整的无向图,利用kruskal来找到最小生成树,就能实现最小消耗的mp值。
实现如下:通过建立一个edge的起点,终点,边权结构体实现图形结构,然后将边权从小到大排列。接着从最小边权开始,将不相同两点的边权利用并查集合并 并将消耗的Mp(边权)累加,直到合并完所有的顶点即可。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
struct edge
{
int u;
int v;
int w;
edge() {};
edge(int u1,int v1,int w1)
{
u = u1;
v = v1;
w = w1;
}
friend bool operator <(const edge& e1, const edge& e2)
{
return e1.w < e2.w;
}
}e[100001];
int pre[500001];
void makefather(int n) //造爹
{
for (int i = 0; i <= n; i++)
pre[i] = i;
}
int findfather(int i) //找爹
{
while (pre[i] != i)
{
pre[i] = pre[pre[i]];
i = pre[i];
}
return i;
}
void union1(int a, int b) //认爹
{
pre[findfather(b)] = findfather(a);
}
int kruskal(int count,int n)
{
makefather(n);
int ans = 0, j = 0;
for (int i = 0; i < count; i++)
{
if (j == n) break;
if (findfather(e[i].u) != findfather(e[i].v))
{
union1(e[i].u, e[i].v);
ans += e[i].w;
j++;
}
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
int w, count=0;
for (int i = 1; i <=n; i++)
{
cin >> w;
e[count++] = edge(0, i, w);
e[count++] = edge(i, 0, w);
}
for(int i=1;i<=n;i++)
for (int j = 1; j <= n; j++)
{
cin >> w;
e[count++] = edge(i, j, w);
}
sort(e, e + count);
int ans = kruskal(count, n);
cout << ans << endl;
return 0;
}
D-数据中心
题意:
思路:
这道题有个迷惑项:root,也许会思考root好像对这道题的解决没有任何作用,事实正是如此。
这道题的思路和上道题一样,唯一不同的就是需要修改一下kruskal,找到最小生成树记录边权的最大值即可。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct graph
{
int u;
int v;
int t;
graph() {}
graph(int u1, int v1, int t1)
{
u = u1;
v = v1;
t = t1;
}
friend bool operator<(const graph& g1, const graph& g2)
{
return g1.t < g2.t;
}
}g[100000];
int pre[100000];
void makefather(int n) //造爹
{
for (int i = 0; i <= n; i++)
pre[i] = i;
}
int findfather(int i) //找爹
{
while (pre[i] != i)
{
pre[i] = pre[pre[i]];
i = pre[i];
}
return i;
}
void union1(int a, int b) //认爹
{
pre[findfather(b)] = findfather(a);
}
int kruskal(int count, int n)
{
makefather(n);
int j = 0,maxt=0;
for (int i = 0; i < count; i++)
{
if (j == n-1) break;
if (findfather(g[i].u) != findfather(g[i].v))
{
maxt = max(maxt, g[i].t);
union1(g[i].u, g[i].v);
j++;
}
}
return maxt;
}
int main()
{
ios::sync_with_stdio(false);
int n, m, root;
cin >> n >> m >> root;
for (int i = 0; i < m; i++)
{
cin >> g[i].u >> g[i].v >> g[i].t;
}
sort(g, g + m);
int max1 = kruskal(m, n);
cout << max1 << endl;
return 0;
}