【杂题合集】震惊!这种生活习惯可能致癌!99%的人都有......
杂题合集
基本上都是不太难的思维题,难的我也写不出来。
标题党去死。
CF1714E Add Modulo 10
题意概述:
给出一个序列,可以给其中任意一个数加上当前它的个位数,问进行若干次操作后这个序列里的数能否全部相等。
解析:
思维题,不算难。
观察个位数,找规律:
- 个位数是 \(0\):只能是 \(0\)。
- 个位数是 \(1\) :\(1 → 2\)。
- 个位数是 \(2\) :\(2 → 4 → 8 → 6 → 2\)。
- 个位数是 \(3\) :\(3 → 6 → 2\)。
- 个位数是 \(4\):\(4 → 8 → 6 → 2\)。
- 个位数是 \(5\):\(5 → 0\)。
- 个位数是 \(6\):\(6 → 2\)。
- 个位数是 \(7\):\(7 → 4 → 8 → 6 →2\)。
- 个位数是 \(8\):\(8 → 6 → 2\)。
- 个位数是 \(9\):\(9 → 8 → 6 →2\)。
可见,除个位数为 \(0\) 和 \(5\) 外,所有的数都可将个位数变为 \(2\)。
对于个位数是 \(5\) 的数,就给它加上 \(5\),此时只有其他数都与它相等,序列才合法。
对于其他数,都把它变成以 \(2\) 为个位数的数。注意到个位数从 \(2\) 再变到 \(2\),相当于加了 \(20\)。
将变化后的数列sort
一遍,判断相邻的两数之差是不是 \(20\) 的倍数就行了。
Code
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 2e5 + 10;
int t, n;
int num[MAXN];
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
bool Check(){
sort(num + 1, num + 1 + n);
if(num[1] % 10 == 0){
if(num[n] == num[1]) return true;
else return false;
}
for(register int i = 2; i <= n; i++){
int cut = num[i] - num[i - 1];
if(cut % 20) return false;
}
return true;
}
int main(){
t = read();
while(t--){
n = read();
for(register int i = 1; i <= n; i++){
num[i] = read();
if(num[i] % 10 != 2){
switch(num[i] % 10){
case 1 : num[i] += 1; break;
case 3 : num[i] += 9; break;
case 4 : num[i] += 18; break;
case 5 : num[i] += 5; break;
case 6 : num[i] += 6; break;
case 7 : num[i] += 25; break;
case 8 : num[i] += 14; break;
case 9 : num[i] += 23; break;
}
}
}
if(Check()) puts("Yes");
else puts("No");
}
return 0;
}
CF1711B Party
题目概述:
给出一个 \(n\) 个点,\(m\) 条边的无向图,问删去多少的点使得删去的点权最小,且留下的点组成的图中有偶数条边。输出删去的点的点权和。
解析:
如果 \(m\) 为偶数,不用删点。
如果 \(m\) 为奇数,删点的同时删掉的边要为奇数条,大力分类讨论:
- 删一个点:显然要选择一个度数为奇数的点。
- 删两个点:再度大力分类讨论:
- 删去两个相连的,且度数都为偶数的点。
- 删去两个相连的,且度数都为奇数的点。
- 删去不相连的,度数一奇一偶的点:
但是我们可以只删去那个度数为奇数的点,这样做会更优,所以这种情况一定不会有最优解。
- 删三个点:先考虑删去一个度数为奇,两个度数为偶且两两不相连的点。但这样完全可以只删去那个度数为奇的点,所以删去三个点一定不会有最优解。
所以,最优解只有可能在三种情况中出现,即删掉一个度数为奇的点、删掉有相连的两个度数都为奇的点、删掉相连的两个度数都为偶数的点。
因为 \(n,m ≤ 10 ^ {5}\),删去一个点的直接枚举点,删去两个点的直接枚举边即可。
Code
点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 10, MAXM = 1e5 + 10;
int t, n, m, ans;
int unhap[MAXN];
int from[MAXM], to[MAXM], deg[MAXN];
inline void Clear(){
ans = 2147483647;
memset(deg, 0, sizeof(deg));
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
t = read();
while(t--){
Clear();
n = read(), m = read();
for(register int i = 1; i <= n; i++)
unhap[i] = read();
for(register int i = 1; i <= m; i++){
int u, v;
u = read(), v = read();
deg[u]++, deg[v]++;
from[i] = u, to[i] = v;
}
if(!(m & 1)){
puts("0");
continue;
}
for(register int i = 1; i <= n; i++)
if(deg[i] & 1) ans = min(ans, unhap[i]);
for(register int i = 1; i <= m; i++){
int u = from[i], v = to[i];
if(!((deg[u] + deg[v]) & 1))
ans = min(ans, unhap[u] + unhap[v]);
}
printf("%d\n", ans);
}
return 0;
}
CF1605C Dominant Character
题目概述:
给定一个由 abc
组成的字符串,找出一个最短的子串,满足 a
的数量大于 b
的数量和 c
的数量。
解析:
大力讨论:
- 串长为 \(2\),两个
a
相邻。 - 串长为 \(3\),所以仅当两个
a
中间隔了一个b
或c
,即abc
和aca
。 - 串长为 \(4\),
a
的数量至少是 \(2\),a
不能相邻,只能是abca
或acba
,不会有 \(3\) 个a
的情况。 - 串长为 \(5\),
a
的数量至少是 \(3\),则会有长为 \(3\) 的子串中有两个a
,不成立。 - 串长为 \(6\),
a
的数量至少是 \(3\),会出现长为 \(3\) 的子串中有两个a
,不成立。 - 串长为 \(7\),
a
的数量至少是 \(4\),且a
之间需要有三个不是a
的字母隔开。
之后以此类推,发现答案只可能到 \(7\),再长的子串都能都被拆分成更短的满足要求的子串,之后枚举即可。
点击查看代码
Code
#include<cstdio>
using namespace std;
const int MAXN = 1e6 + 10;
int t, n;
char s[MAXN];
int main(){
scanf("%d", &t);
while(t--){
bool flag = false;
scanf("%d", &n);
scanf("%s", s + 1);
for(register int i = 2; i <= n && !flag; i++){
if(s[i] == 'a' && s[i - 1] == 'a'){
puts("2");
flag = true;
}
}
if(n >= 3)
for(register int i = 2; i <= n - 1 && !flag; i++){
if(s[i - 1] == 'a' && s[i + 1] == 'a'){
puts("3");
flag = true;
}
}
if(n >= 4)
for(register int i = 2; i <= n - 2 && !flag; i++){
if(s[i - 1] == 'a' && s[i + 2] == 'a' && ((s[i] == 'b' && s[i + 1] == 'c') || (s[i] == 'c' && s[i + 1] == 'b'))){
puts("4");
flag = true;
}
}
if(n >= 7)
for(register int i = 4; i <= n - 3 && !flag; i++){
if(s[i] == 'a' && s[i - 3] == 'a' && s[i + 3] == 'a'){
if(s[i - 1] == 'b' && s[i - 2] == 'b' && s[i + 1] == 'c' && s[i + 2] == 'c'){
puts("7");
flag = true;
}
else if(s[i - 1] == 'c' && s[i - 2] == 'c' && s[i + 1] == 'b' && s[i + 2] == 'b'){
puts("7");
flag = true;
}
}
}
if(!flag) puts("-1");
}
return 0;
}
CF1714G Path Prefixe
题目概述:
给你一颗以一号节点为根的树,每个节点上有两个权值,分别为 \(a_{i}\) 和 \(b_{i}\)。记 \(A_{i}\) 为从根到节点 \(i\) 的 \(a_{i}\) 的前缀和,求最长的使得 \(b_{i}\) 的和小于等于 \(A_{i}\) 的前缀。
解析:
其实题目概述写啰嗦了,我的确概述不了这道题。读懂了题就很简单了。
看数据范围 \(n ≤ 2 \times 10 ^ {5}\),以及题目中的最长、不大于,可以往二分上靠。
树论的话 dfs
是必不可少的,用 dfs
求出每个节点 \(a\) 和 \(b\) 的前缀和,把当前节点的 \(b\) 的前缀和压进栈里,本层的 dfs
结束时再把它弹出栈,就可以保证栈里的元素全部是从根节点到当前节点路径上,每个点的 \(b\) 的前缀和,upper_bound
一下即可求出最长的前缀。
注意开 long long
,CF Div.3 光卡 。long long
Code
点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int MAXN = 2e5 + 10;
int t, n, cnt, top;
int head[MAXN], ans[MAXN];
LL sum_a[MAXN], sum_b[MAXN];
LL stk[MAXN];
struct Edge{
int to, next, dis_a, dis_b;
}e[MAXN << 1];
inline void Add(int u, int v, int a, int b){
e[++cnt].to = v;
e[cnt].dis_a = a;
e[cnt].dis_b = b;
e[cnt].next = head[u];
head[u] = cnt;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
void dfs(int rt, int fa){
stk[++top] = sum_b[rt];
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == fa) continue;
sum_a[v] = sum_a[rt] + e[i].dis_a;
sum_b[v] = sum_b[rt] + e[i].dis_b;
dfs(v, rt);
}
int pos = upper_bound(stk + 1, stk + 1 + top, sum_a[rt]) - stk;
ans[rt] = pos - 2;
top--;
}
void Clear(){
cnt = 0;
top = 0;
memset(head, 0, sizeof(head));
}
int main(){
t = read();
while(t--){
Clear();
n = read();
for(register int i = 2; i <= n; i++){
int p, a, b;
p = read(), a = read(), b = read();
Add(p, i, a, b);
Add(i, p, a, b);
}
dfs(1, 0);
for(register int i = 2; i <= n; i++)
printf("%d ", ans[i]);
puts("");
}
return 0;
}
CF1714F Build a Tree and That Is It
题目概述:
概不出来了自己品吧。
树是一个没有环的无向连通图,注意,在本题中,我们讨论的是无根树
现有四个整数 \(n, d_{12}, d_{23}\) 和 \(d_{31}\),构建一颗满足以下条件的树:
- 包含从 \(1\) 到 \(n\) 的 \(n\) 个节点;
- 从节点 \(1\) 到节点 \(2\) 的距离(最短路的长度)为 \(d_{12}\);
- 从节点 \(2\) 到节点 \(3\) 的距离为 \(d_{23}\);
- 从节点 \(3\) 到节点 \(1\) 的距离为 \(d_{31}\)。
输出满足条件的任意一棵树;若不存在,输出 NO
。
解析:
每条边的边权为 \(1\)。
题目说是无根树,可以先假设它有一个根节点,设 \(d_{1}\),\(d_{2}\),\(d_{3}\) 三条互不相交的分别连接根节点和 \(1\),\(2\),\(3\) 号节点的链的长度。
然后就可得到:
只后我们移项,消元:
其中 \(d_{1}\),\(d_{2}\),\(d_{3}\) 可能为 \(0\),代表该节点就是根节点。
至于无解的情况,显然,\(d_{1}\),\(d_{2}\),\(d_{3}\) 都要 \(≥0\),且\(d_{12} + d_{31} - d_{23}\),\(d_{12} + d_{23} - d_{31}\),\(d_{23} + d_{31} - d_{12}\) 都应为偶数。
构造完这 \(3\) 条链后的树即为满足 \(3\) 个点之间的约束条件的最小的树,判断总的节点数 \(tot = d_{1} + d{2} + d{3} + 1\) (从根节点到 \(1\),\(2\),\(3\) 号节点的链的长度就是链上的节点数,最后要加上根节点)与 \(n\) 的大小关系:
- \(tot < n\),则剩余的节点在任意节点上都不会影响树的合法性。
- \(tot = n\),构造结束。
- \(tot > n\),不合法,构造失败。
之后我们需要找出根节点是谁,若 \(d_{1}\),\(d_{2}\),\(d_{3}\) 都不为 \(0\),我们可以让任意一个 \(>3\) 且 \(≤n\) 的节点为根节点,这里选择了 \(4\) 号节点。
最后剩下的节点随便连谁都可以,这里选择接在 \(3\) 号节点。
Code
点击查看代码
#include<cstdio>
using namespace std;
int t, n, d12, d23, d31;
int root, tot, d[4];
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
void Build_Tree(int x){
printf("%d ", root);
for(register int i = 1; i < d[x]; i++){
++tot;
printf("%d\n", tot);
printf("%d ", tot);
}
printf("%d\n", x);
}
void Clear(){
tot = 0;
root = 0;
d[1] = d[2] = d[3] = 0;
}
int main(){
t = read();
while(t--){
Clear();
n = read();
d12 = read(), d23 = read(), d31 = read();
d[1] = (d12 + d31 - d23) / 2;
d[2] = (d12 + d23 - d31) / 2;
d[3] = (d23 + d31 - d12) / 2;
if(d[1] + d[2] + d[3] + 1 > n || d[1] < 0 || d[2] < 0 || d[3] < 0){
puts("NO");
continue;
}
if((d12 + d31 - d23) & 1 || (d12 + d23 - d31) & 1 || (d23 + d31 - d12) & 1){
puts("NO");
continue;
}
puts("YES");
tot = 3;
if(!d[1]) root = 1;
if(!d[2]) root = 2;
if(!d[3]) root = 3;
if(!root){
tot = 4;
root = 4;
}
if(1 != root) Build_Tree(1);
if(2 != root) Build_Tree(2);
if(3 != root) Build_Tree(3);
while(tot < n) printf("3 %d\n", ++tot);
}
return 0;
}
P7913 [CSP-S 2021] 廊桥分配
没考试,刷点历年原题。看着它是21年的T1,应该好欺负,然后想了一中午才想出来。
题目概述:
概NM,自己看。
解析:
廊桥先到先得,仅于飞机的起降顺序有关,加入第 \(i\) 条廊桥不会对前 \(i - 1\) 条廊桥的分配产生影响。我们设 \(f(x)\) 代表分配 \(x\) 条廊桥能停靠的国内航班数,\(g(x)\) 代表分配 \(x\) 条廊桥能停靠的国际航班数,可得 \(f(x) = f(x - 1) + 再加入一个廊桥产生的贡献\),\(g(x) = g(x - 1) + 再加入一个廊桥产生的贡献\)。
考虑每加入一条廊桥会影响哪些飞机。用 set
来维护当前在远机位和未降落的飞机。每加入一条廊桥,当前 set
顶端的飞机 \(p\) 停靠到廊桥,之后,set
中在 \(p\) 起飞后第一个降落的飞机 \(p'\) 停靠到廊桥......以此类推。
直接在 set
中二分查找,记录次数,然后再把找到的飞机清出 set
(停靠过一遍廊桥即起飞),最后加入这条廊桥的贡献就是找到的飞机的次数。
Code
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 10;
int n, m1, m2, ans;
int sum1[MAXN], sum2[MAXN];
struct Plane{
int st, ed;
bool operator < (const Plane &a) const{
return st < a.st;
}
}p1[MAXN], p2[MAXN];
multiset<Plane> s1, s2;
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int Get_Num(multiset<Plane> &s){
int ans = 0;
auto pos = s.begin();
if(pos == s.end()) return 0;
Plane p = *pos;
p.st = p.ed;
++ans;
s.erase(pos);
while(!s.empty()){
pos = s.lower_bound(p);
if(pos == s.end()) break;
++ans;
p = *pos;
p.st = p.ed;
s.erase(pos);
}
return ans;
}
int main(){
n = read(), m1 = read(), m2 = read();
for(register int i = 1; i <= m1; i++){
p1[i].st = read(), p1[i].ed = read(); //国内航班
s1.insert(p1[i]);
}
for(register int i = 1; i <= m2; i++){
p2[i].st = read(), p2[i].ed = read(); //国外航班
s2.insert(p2[i]);
}
sort(p1 + 1, p1 + 1 + m1);
sort(p2 + 1, p2 + 1 + m2);
for(register int i = 1; i <= n; i++)
sum1[i] = sum1[i - 1] + Get_Num(s1);
for(register int i = 1; i <= n; i++)
sum2[i] = sum2[i - 1] + Get_Num(s2);
for(register int i = 0; i <= n; i++)
ans = max(ans, sum1[i] + sum2[n - i]);
printf("%d", ans);
return 0;
}
P2515 [HAOI2010]软件安装
边写边颓,结果一A了(坏了,我成wonder了)。
题目概述:
自己概去,语文功底不好,概不出来。
解析:
最开始当图论题打的,结果越打越不对,发现是个树上的背包问题。
首先,每个软件之间有依赖关系,即软件 \(i\) 只有在安装了软件 \(D_i\) 之后才能正常工作,考虑从 \(D_i\) 向 \(i\) 连边,最后会得到一个类似树形的关系。然后就理所应当的想到拓扑排序或者树形DP了是吧。
问题在于这个关系并不能直接构成树,每个关系(每条边)都是单向的,所以可能出现游离在外的点或环,比如 \(D_1 = 2, D_2 = 3, D_3 = 1\),这时 \(1, 2, 3\) 就形成了一个独立的环。这个环要么都选,要么都不选,所以可以用tarjan缩点,把环缩成一个联通块。
缩完点之后仍然有独立的点,需要把它变成一个树状结构。考虑将所有入度为 \(0\) 的点和 \(0\) 连一条边,这样这张图就可以转化成一棵以 \(0\) 为根的树,然后就可以在树上跑01背包了。
设定状态 \(dp[i][j]\) 代表以 \(i\) 为根的子树在 \(j\) 的容量下能得到的最大的价值,在递归到当前节点的时候记得初始化一下DP数组就行了。为啥不讲转移?就是个普通的01背包,讲啥啊。
Code
点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 110, MAXM = 510;
int n, m, cnt, num, tot;
int head[MAXN], spa[MAXN], val[MAXN], from[MAXN], to[MAXN];
int dfn[MAXN], low[MAXN], belong[MAXN], room[MAXN], value[MAXN], in[MAXN];
int stk[MAXN], top;
int dp[MAXN][MAXM];
bool vis[MAXN];
struct Edge{
int to, next;
}e[MAXN << 1];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
void Tarjan(int u){
dfn[u] = low[u] = ++num;
stk[++top] = u;
vis[u] = true;
for(register int i = head[u]; i; i = e[i].next){
int v = e[i].to;
if(!dfn[v]){
Tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(vis[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u]){
++tot;
int t;
do{
t = stk[top--];
vis[t] = false;
belong[t] = tot;
room[tot] += spa[t];
value[tot] += val[t];
}while(t != u);
}
}
void Rebuild(){
cnt = num = 0;
memset(head, 0, sizeof(head));
for(register int i = 1; i <= n; i++){
int u = from[i], v = to[i];
int blu = belong[u], blv = belong[v];
if(!u) continue;
if(blu != blv){
Add(blu, blv);
++in[blv];
}
}
for(register int i = 1; i <= tot; i++)
if(!in[i]) Add(0, i);
}
void dfs(int rt){
for(register int i = room[rt]; i <= m; i++)
dp[rt][i] = value[rt];
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
dfs(v);
int k = m - room[rt];
for(register int i = k; i >= 0; i--)
for(register int j = 0; j <= i; j++)
dp[rt][i + room[rt]] = max(dp[rt][i + room[rt]], dp[v][j] + dp[rt][i + room[rt] - j]);
}
}
int main(){
n = read(), m = read();
for(register int i = 1; i <= n; i++)
spa[i] = read();
for(register int i = 1; i <= n; i++)
val[i] = read();
for(register int i = 1; i <= n; i++){
int u;
u = read();
if(u) Add(u, i);
from[i] = u, to[i] = i;
}
for(register int i = 1; i <= n; i++)
if(!dfn[i]) Tarjan(i);
Rebuild();
dfs(0);
printf("%d\n", dp[0][m]);
return 0;
}
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16667186.html