A - Minimum’s Revenge
Mr. Frog is wondering about the total weight of the minimum spanning tree. Can you help him?
For each test case, the first line contains only one integer n ( 2≤n≤1092≤n≤109), indicating the number of vertices.
OutputFor each test case, output one line "Case #x:y",where x is the case number (starting from 1) and y is the total weight of the minimum spanning tree.
Sample Input
2 2 3Sample Output
Case #1: 2
Case #2: 5
Hint
In the second sample, the graph contains 3 edges which are (1, 2, 2), (1, 3, 3) and (2, 3, 6). Thus the answer is 5.
A:编号为1~n的n个节点,任意两点之间的边权为两点编号的LCM,问这个图的最小生成树权值。
最水的签到题,这道题一开始开场就看到的水题,然后因为把LCM看成GCD还蒙了一下,整明白之后知道是到求和题就直接码了,还担心罚时所以检查了一遍才交 TAT
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long n, sum = 0;
int T;
scanf("%d", &T);
for(int i=1; i<=T; i++){
scanf("%lld", &n);
sum = (2+n)*(n-1)/2;
printf("Case #%d: %lld\n", i, sum);
}
}
He gives you two positive integers A and B, and your task is to find all pairs of integers (C, D), such that A≤C≤B,A≤D≤Band AB+BA≤CD+DC
Then in a new line, print an integer s indicating the number of pairs you find.
In each of the following s lines, print a pair of integers C and D. pairs should be sorted by C, and then by D in ascending order.
Sample Input
2 10 10 9 27Sample Output
Case #1: 1 10 10 Case #2: 2 9 27 27 9
C题:给定区间A, B求解满足要求的CD,并按照C的升序输出。
这是我们队全场A出的第二题,也是一道签到题,当时莽了一发特判AB相等不然互换的结论就过了,场后学妹表示可以用斜率证明,学妹V5。
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long a, b;
int T;
scanf("%d", &T);
for(int i=1; i<=T; i++){
scanf("%lld%lld", &a, &b);
printf("Case #%d:\n", i);
if(a == b){
printf("1\n");
printf("%lld %lld\n", a, b);
}
else {
printf("2\n");
long long aa = min(a, b);
long long bb = max(a, b);
printf("%lld %lld\n", aa, bb);
printf("%lld %lld\n", bb, aa);
}
}
return 0;
}
E - Mr. Frog’s Game
In this game, if you can draw at most three horizontal or vertical head-and-tail-connected lines over the empty grids(the lines can be out of the whole board) to connect two non-empty grids with the same symbol or the two non-empty grids with the same symbol are adjacent, then you can change these two grids into empty and get several more seconds to continue the game.
Now, Mr. Frog starts a new game (that means there is no empty grid in the board). If there are no pair of grids that can be removed together,Mr. Frog will say ”I’m angry” and criticize you.
Mr. Frog is battle-scarred and has seen many things, so he can check the board in a very short time, maybe one second. As a Hong Kong Journalist, what you should do is to check the board more quickly than him, and then you can get out of the room before Mr. Frog being angry.
For each test case, the first line contains two integers n and m ( 1≤n,m≤301≤n,m≤30).
In the next n lines, each line contains m integers, j-th number in the i-th line means the symbol on the grid(the same number means the same symbol on the grid).
OutputFor each test case, there should be one line in the output.
You should output “Case #x: y”,where x is the case number(starting from 1), and y is a string representing the answer of the question. If there are at least one pair of grids that can be removed together, the y is “Yes”(without quote), else y is “No”.Sample Input
2 3 3 1 2 1 2 1 2 1 2 1 3 3 1 2 3 2 1 2 3 2 1Sample Output
Case #1: Yes Case #2: No
E题:E题是学妹做的,听说是一道只判边缘相同的连连看水题,并没有看...直接放ac代码吧。
#include <bits/stdc++.h>
using namespace std;
const int maxn=33;
int mp[maxn][maxn];
int meo[4][2]= {{1,0},{-1,0},{0,1},{0,-1}};
int n,m;
bool check()
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i==j) continue;
if(mp[i][m]==mp[j][m])
return true;
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i==j) continue;
if(mp[i][1]==mp[j][1])
return true;
}
}
for(int i=1; i<=m; i++)
{
for(int j=1; j<=m; j++)
{
if(i==j) continue;
if(mp[n][i]==mp[n][j])
return true;
}
}
for(int i=1; i<=m; i++)
{
for(int j=1; j<=m; j++)
{
if(i==j) continue;
if(mp[1][i]==mp[1][j])
return true;
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
for(int t=0; t<4; t++)
{
int x=i+meo[t][0];
int y=j+meo[t][1];
if(mp[x][y]==mp[i][j])
return true;
}
}
}
return false;
}
int main()
{
int t;
cin>>t;
int cas=1;
while(t--)
{
// int n,m;
cin>>n>>m;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin>>mp[i][j];
}
}
//cas=1;
//cout<<"Case #"<<cas++<<':'<<' ';
printf("Case #%d: ",cas++);
if(check())
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
return 0;
}
H - Basic Data Structure
∙∙ PUSH x: put x on the top of the stack, x must be 0 or 1.
∙∙ POP: throw the element which is on the top of the stack.
Since it is too simple for Mr. Frog, a famous mathematician who can prove "Five points coexist with a circle" easily, he comes up with some exciting operations:
∙∙REVERSE: Just reverse the stack, the bottom element becomes the top element of the stack, and the element just above the bottom element becomes the element just below the top elements... and so on.
∙∙QUERY: Print the value which is obtained with such way: Take the element from top to bottom, then do NAND operation one by one from left to right, i.e. If atop,atop−1,⋯,a1atop,atop−1,⋯,a1 is corresponding to the element of the Stack from top to the bottom, value=atopvalue=atop nand atop−1atop−1 nand ... nand a1a1. Note that the Stack will not change after QUERY operation. Specially, if the Stack is empty now,you need to print ” Invalid.”(without quotes).
By the way, NAND is a basic binary operation:
∙∙ 0 nand 0 = 1
∙∙ 0 nand 1 = 1
∙∙ 1 nand 0 = 1
∙∙ 1 nand 1 = 0
Because Mr. Frog needs to do some tiny contributions now, you should help him finish this data structure: print the answer to each QUERY, or tell him that is invalid.
For each test case, the first line contains only one integers N ( 2≤N≤2000002≤N≤200000), indicating the number of operations.
In the following N lines, the i-th line contains one of these operations below:
∙∙ PUSH x (x must be 0 or 1)
∙∙ POP
∙∙ REVERSE
∙∙ QUERY
It is guaranteed that the current stack will not be empty while doing POP operation.
OutputFor each test case, first output one line "Case #x:w, where x is the case number (starting from 1). Then several lines follow, i-th line contains an integer indicating the answer to the i-th QUERY operation. Specially, if the i-th QUERY is invalid, just print " Invalid."(without quotes). (Please see the sample for more details.)
Sample Input
2 8 PUSH 1 QUERY PUSH 0 REVERSE QUERY POP POP QUERY 3 PUSH 0 REVERSE QUERYSample Output
Case #1: 1 1 Invalid. Case #2: 0
H:模拟一个可以翻转的栈,包含PUSH x; POP; REVERSE;QUERY查询的是从栈顶往下做与非运算的结果。
开场想到可以用双端队列(deque)模拟这个可以翻转的栈,之后在讨论中判断出最后的值只和栈底连续的1的个数有关,于是决定将连续的1以计数的方式入队,这样每次查询的时候可以直接O1判断出最后的结果。但是犯错WA了几发,原因是我认为从栈底往上第一个0相当于将之前全部置1,因为0与非任何数都得0。所以就只加了对size==1的判断,但是经过学弟提醒,当你只有一个1的时候,0代表的就是0他是没有计算过与非的。于是就又加了size==2的判断。学弟V5
#include<bits/stdc++.h>
using namespace std;
deque<int > dq;
char op[20];
int main()
{
int T, cas = 1;
scanf("%d", &T);
while(T--)
{
int flag = 1;
dq.clear();
int n;
scanf("%d", &n);
printf("Case #%d:\n", cas++);
while(n--)
{
scanf("%s", op);
if(op[2] == 'S') //push
{
int num;
scanf("%d", &num);
if(dq.empty())
dq.push_back(num);
else if(!num) {// num == 0
if(flag) // front is top
dq.push_front(num);
else // back is top
dq.push_back(num);
}
else { // num == 1
if(flag){ // front is top
int t = dq.front();
if(t==0)
dq.push_front(num);
else {
dq.pop_front();
dq.push_front(t+1);
}
}
else{ // back is top
int t = dq.back();
if(t==0)
dq.push_back(num);
else {
dq.pop_back();
dq.push_back(t+1);
}
}
}
}
else if(op[2] == 'V') //reverse
flag ^= 1; //reverse
else if(op[2] == 'P') // pop
{
if(flag){ // front is top
int t = dq.front();
if(t == 0 || t == 1)
dq.pop_front();
else{
dq.pop_front();
dq.push_front(t-1);
}
}
else { // back is top
int t = dq.back();
if(t == 0 || t == 1)
dq.pop_back();
else{
dq.pop_back();
dq.push_back(t-1);
}
}
}
else if(op[2] == 'E') // query
{
int t;
if(dq.empty()){
puts("Invalid.");
continue;
}
else if(flag) // front is top
t = dq.back();
else // back is top
t = dq.front();
if(dq.size()==1) {
if(t == 0) puts("0");
else if(t%2) puts("1");
else puts("0");
}
else if(dq.size() == 2){
if(t==0) puts("1");
else if(t%2) puts("1");
else puts("0");
}
else{
if(t == 0) puts("1");
else if(t%2) puts("0");
else puts("1");
}
}
// for(deque<int>::iterator it = dq.begin(); it!=dq.end(); it++)
// printf("%d ", *it);
// puts("");
}
}
return 0;
}
这道题如果把PUSH、POP、QUE、REV操作全部拉出来单列成函数可能会更加清晰一点,但是我懒得弄了,场上是按做一个复杂模拟写的Orz
三个小时的训练场,A出来四题,之后的是补出来的两题。
F - Auxiliary Set
An auxiliary set is a set containing vertices satisfying at least one of the two conditions:
∙∙It is an important vertex
∙∙It is the least common ancestor of two different important vertices.
You are given a tree with n vertices (1 is the root) and q queries.
Each query is a set of nodes which indicates the unimportant vertices in the tree. Answer the size (i.e. number of vertices) of the auxiliary set for each query.
For each test case, the first line contains two integers n ( 1≤n≤100000), q ( 0≤q≤100000 ).
In the following n -1 lines, the i-th line contains two integers ui,vi(1≤ui,vi≤n)ui,vi(1≤ui,vi≤n)indicating there is an edge between uiuii and vivi in the tree.
In the next q lines, the i-th line first comes with an integer mi(1≤mi≤100000) indicating the number of vertices in the query set.Then comes with mi different integers, indicating the nodes in the query set.
It is guaranteed that ∑qi=1mi≤100000.
It is also guaranteed that the number of test cases in which n≥1000 or ∑qi=1mi≥1000 is no more than 10.
OutputFor each test case, first output one line "Case #x:", where x is the case number (starting from 1).
Then q lines follow, i-th line contains an integer indicating the size of the auxiliary set for each query.
Sample Input
1 6 3 6 4 2 5 5 4 1 5 5 3 3 1 2 3 1 5 3 3 1 4Sample Output
Case #1: 3 6 3
F题:题目给出一颗根为1的有根树。然后每次给出你一个不重要点的集合(其他点都是重要的),问你好点的个数。而好点有两个定义,一个是它本身是重要的,第二个是它是两个好点的LCA
一开始以为这道题要用LCA去写,但是因为不愿意(其实是因为不会)所以决定用pre数组去存前驱节点的信息,然后往回遍历的方式去把不重要的点转换求解,然后WA了。
第二天补题时候打算去学一下LCA但是回头看看这道题,不重要的点个数偏少,而重要的点特别的多,如果我枚举重要的点对,然后再去找他们的LCA,这个时间复杂度其实是O(n^2logn)的,很明显对于此中数据并不能通过,然后想了想一定是有什么高端的LCA算法是我没有掌握的,但是一时半会儿又不想去学,就打算回归题目本身再去想一下。
之后观察到不重要的点个数很少,那我们能不能通过判断不重要的点能不能转化为好点来求解。然后我们得到了不重要的点嗯那个转化为好点的条件:他的所有子节点为根的子树中,只要有两个以上子树中含有重要的点,那么这个节点就是两个重要的点的LCA。那么我们就变成了,按照深度降序访问所有不重要的节点(深度降序保证了之前的访问不会影响到之后的),如果当前不好的节点没有子节点,那么它一定是坏点,且它引导的子树对父亲节点没有贡献,那么就让父亲节点的子节点数--, 如果它有一个子节点,那么这个点不能转化为好点,但是它引导的子树依然有贡献,不做处理。如果它有两个以上的子节点,那么也就是说这个点可以转化为好点,且对父亲节点有贡献,cnt++
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
struct Node{
int num = 0; //子节点个数(分支个数)
int pre = -1; //父亲节点编号
int id = 1; //当前节点编号
int val = -1; //用于在同一个图的不同询问中,判断有贡献的分支数量
int dep; //树深,dfs中赋值,用于在set中排序
bool operator < (const Node &b) const{ // sort in set depend on depth
//重写小于运算符,便于在set中排序
if(dep!=b.dep)
return dep > b.dep; //第一排序方式是深度
else return id < b.id; //第二排序方式是id, 不加这个判断的话,会因为相同深度被set去重
}
}a[maxn];
set<Node >s;
vector<int >edge[maxn];
bool vis[maxn];
void init(int n) // init a[1, n]
{
for(int i=0; i<maxn; i++)
edge[i].clear();
memset(vis, 0, sizeof vis);
vis[1] = 1;
for(int i=0; i<=n+1; i++){
a[i].id = i;
a[i].num = 0;
a[i].val = -1;
}
return ;
}
void dfs(int n, int dep)
{
a[n].dep = dep;
//printf("***%d: %d\n", n, dep);
for(int i=0; i<edge[n].size(); i++)
{
if(!vis[edge[n][i]]){
vis[edge[n][i]]++;
a[n].num++;
a[edge[n][i]].pre = n;
dfs(edge[n][i], dep+1);
}
}
}
void solve()
{
int n, k;
scanf("%d%d", &n, &k);
init(n); //每个cas都需要初始化
for(int i=0; i<n-1; i++)
{
int u, v;
scanf("%d%d", &u, &v);
edge[v].push_back(u);
edge[u].push_back(v);
}
dfs(1, 1); //build the tree
for(int i=0; i<k; i++)
{
s.clear();
memset(vis, 0, sizeof vis); //标记是否需要重读 a[i].num
int m, p;
scanf("%d", &m);
int cnt = n-m; // important nodes
Node now;
for(int i=0; i<m; i++)
{
scanf("%d", &p);
now = a[p];
s.insert(now);
}
for(set<Node>::iterator it=s.begin(); it!=s.end(); it++)
{
//printf("##%d %d %d\n", (*it).id, a[(*it).id].val, cnt);
if(!vis[(*it).id]){ // not valued
a[(*it).id].val = a[(*it).id].num;
vis[(*it).id] = 1;
}
if(a[(*it).id].val == 0){ // 当前分支无贡献
if(!vis[(*it).pre]){
a[(*it).pre].val = a[(*it).pre].num;
vis[(*it).pre] = 1;
}
a[(*it).pre].val--;
}
else if(a[(*it).id].val == 1); // 当前分支对父亲节点有贡献(当前分支下有一个important node)
else if(a[(*it).id].val >= 2){ // 当前节点下的分支有两个以上贡献,当前节点可以加入集合cnt++.
cnt++; // unimportant into set
}
}
printf("%d\n", cnt);
}
}
int main()
{
int T;
scanf("%d", &T);
for(int cas=1; cas<=T; cas++)
{
printf("Case #%d:\n", cas);
solve();
}
return 0;
}
D - Coconuts
Now TanBig wants to know how many times he needs to eat all the good coconuts in the field, and how many coconuts he would eat each time(the area of each 4-connected component).
It is guaranteed that in the input data, the first row and the last row will not have bad coconuts at the same time, the first column and the last column will not have bad coconuts at the same time.
OutputFor each test case, output "Case #x:" in the first line, where x denotes the number of test case, one integer k in the second line, denoting the number of times TanBig needs, in the third line, k integers denoting the number of coconuts he would eat each time, you should output them in increasing order.Sample Input
2 3 3 2 1 2 2 1 3 3 1 2 2Sample Output
Case #1: 2 1 6 Case #2: 1 8
D题:给出一个1e9*1e9的图,然后给出200个坏点,问由好点组成的四连通块的个数,并且按照升序输出这些联通块的大小。
D题看到坏点这么多好点这么少,第一反应就是用坐标离散化去求解。在学习了挑战上的坐标离散化之后就手码了一发,保留所有坏点来保证坏点包围的联通块大小是准确的,然后判断如果一个联通块四周都能达到边界则这个联通块没有被坏点包围,那么就用总点数-坏点点数-坏点包围的好点点数来求出最大联通块的值,但是这个解法有很多的bug。
之后查了关于坐标离散化的一些内容,其实感觉什么重要的都没有看到,但是想到了既然我们将原本来的坐标映射到了一个离散化之后的图上,那我们同样可知离散化后图上的点,对于原图的映射。此时我们就可以知道,当前这一格,代表着原来的多少格(其实是将行和列分别储存,第i行代表原来多少行,第j列代表原来多少列,然后mp[i][j]这一点对应原来多少点就求出来了)。我们认为的规定离散化去掉的点的值赋给左边,那么我们再特判一下第一行和第一列就可以了。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 205;
int x[maxn], y[maxn], xx[6*maxn], yy[6*maxn]; ///x, y用于存储对应点的坐标
///xx yy 用于存放离散化之后的行列所对应的原行数
int tox[] = {0, -1, 0, 1};
int toy[] = {1, 0, -1, 0};
bool mp[6*maxn][6*maxn]; ///用于存放离散化之后的图
int n, w, h;
int LSH(int *x, int* xx, int R)
{
vector<int >xs;
for(int i=0; i<n; i++)
for(int d=-1; d<=1; d++)
if(x[i]+d>0 && x[i]+d<=R)
xs.push_back(x[i]+d); ///将对应坐标本身和左右读入到xs中,即xs中储存的是我们所有需要保留的坐标值
sort(xs.begin(), xs.end()); ///先sort才能用unique
xs.erase(unique(xs.begin(), xs.end()), xs.end()); ///unique将区间中重复的元素放到数组的最后部分,并返回第一个重复元素的指针(迭代器)
///然后用erase删去重复部分,即xs中按照升序存放了所有要用到的坐标,而xs的下标就是离散化之后对应的坐标
for(int i=0; i<n; i++){
int sign = find(xs.begin(), xs.end(), x[i]) - xs.begin() + 1; ///新的下标 [1, xs.size()]
x[i] = sign; ///x[i]对应到新的下标
}
for(int i=0; i<xs.size(); i++){ ///新的下标所对应的原值,用于判断离散化后节点对应的权值
xx[i+1] = xs[i];
}
xx[0] = 0; ///最小边界为0
xx[xs.size()+1] = R+1; ///最大行为R+1行
// for(int i=0; i<=xs.size()+1; i++)
// printf("%d%c", xx[i], i==xs.size()+1?'\n':' ');
//
// for(int i=1; i<=xs.size(); i++)
// printf("%d%c", xx[i+1]-xx[i-1]-1, i==xs.size()?'\n':' ');
return xs.size();
}
LL bfs(int x, int y)
{
LL cnt = 0;
queue<pair<int, int> >q;
q.push(make_pair(x, y));
mp[x][y] = true;
while(!q.empty())
{
int nowx = q.front().first, nowy = q.front().second;
q.pop();
LL x1 = nowx==1?xx[nowx+1]-xx[nowx-1]-1:xx[nowx+1]-xx[nowx]; ///将省略部分全部给了较小的一行(列)所以要对第一行(列)进行特殊处理
LL y1 = nowy==1?yy[nowy+1]-yy[nowy-1]-1:yy[nowy+1]-yy[nowy];
cnt += x1*y1; ///cnt加上当前单元格所代表的行列乘积
for(int i=0; i<4; i++)
{
int nextx = nowx + tox[i];
int nexty = nowy + toy[i];
if(nextx>0 && nexty>0 && nextx<=w && nexty<=h && !mp[nextx][nexty])
{
q.push(make_pair(nextx, nexty));
mp[nextx][nexty] = true;
}
}
}
return cnt;
}
int main()
{
int T, cas = 1, R, C;
scanf("%d", &T);
while(T--)
{
vector<LL >ans;
memset(mp, false, sizeof(mp));
scanf("%d%d", &R, &C);
scanf("%d", &n);
if(n==0){
printf("Case #%d:\n", cas++);
printf("1\n");
printf("%lld\n", (LL)R*(LL)C);
continue;
}
for(int i=0; i<n; i++){
scanf("%d%d", &x[i], &y[i]);
}
w = LSH(x, xx, R);
h = LSH(y, yy, C);
for(int i=0; i<n; i++)
mp[x[i]][y[i]] = true;
for(int i=1; i<=w; i++)
for(int j=1; j<=h; j++)
{
if(mp[i][j]) continue;
LL temp = bfs(i, j);
ans.push_back(temp);
}
sort(ans.begin(), ans.end());
printf("Case #%d:\n", cas++);
printf("%d\n", (int)ans.size());
for(int i=0; i<ans.size(); i++)
{
printf("%lld%c", ans[i], i==ans.size()-1?'\n':' ');
}
}
}