题解 【第五届传智杯 初赛】
A
题解
签到题。首先用
- 出题的灵感?在库函数里有个处理浮点数用的函数
\operatorname{copysign}(a,b) 就是干这个活的。当然浮点数要复杂得多,比如会有\text{Infinity} 、\text{NaN} 这类奇怪的玩意,以及还有-0 这种特殊的东西。当然本题限定在了整数。 - 题目的坑点?注意到,
32 位有符号整型的范围是-2^{31}\sim (2^{31}-1) ,那么当-2^{31} 取绝对值时就会超过\text{int} 的范围。可以特判/使用范围更大的数据类型。
时间复杂度为
参考代码
版本
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
int main(){
int a, b;
cin >> a >> b;
cout << fixed << setprecision(0) << copysignl(a, b) + 1e-9 << endl;
return 0;
}
版本
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
int main(){
i64 a, b; cin >> a >> b;
cout << (b < 0 ? -llabs(a) : llabs(a));
return 0;
}
版本
#include <bits/stdc++.h>
using namespace std;
int main()
{
int a,b;
cin >> a >> b;
if (b>0 && a==INT_MIN)
cout << 2147483648ll << endl;
else
{
a=abs(a);
if (b<0)
a*=-1;
cout << a << endl;
}
return 0;
}
版本
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long a = scanner.nextLong(), b = scanner.nextLong();
System.out.println((Math.abs(a) * (b > 0 ? 1 : -1)));
}
}
B
题解
模拟题。按照题目要求输入整数
- 出题的灵感?最近有一场
\text{CF} 就考到了第i 位是i 进制的题(当然并没有显式地告诉你算这东西的和,而是要证明一个小结论),但是这题偏难了于是就改成了这样。 - 题目的坑点?主要是细节部分。例如
0+0=0 这个0 也是有1 的长度的,以及要考虑进位后总位数达到了\max(n,m)+1 。
时间复杂度为
参考代码
版本
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
int qread(){
int w=1,c,ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
const int MAXN = 2e5 + 3;
int A[MAXN], B[MAXN];
int main(){
int n = qread(), m = qread(), l = max(n, m);
dn(n, 1, i) A[i] = qread();
dn(m, 1, i) B[i] = qread();
up(1, l, i) A[i] += B[i], A[i + 1] += A[i] / (i + 1), A[i] %= i + 1;
if(A[l + 1]) ++ l;
dn(l, 1, i) printf("%d%c", A[i], " \n"[i == 1]);
return 0;
}
版本
#include <bits/stdc++.h>
using namespace std;
int a[200050],b[200050];
int main()
{
auto read=([&]{
int x;cin >> x;
return x;
});
int n=read(),m=read();
int len=max(n,m)+1;
generate_n(a+1,n,read);
generate_n(b+1,m,read);
reverse(a+1,a+n+1);
reverse(b+1,b+m+1);
for (int i=1;i<=len;i++)
{
a[i]+=b[i];
a[i+1]+=(a[i]/(i+1));
a[i]%=(i+1);
}
while (a[len]==0 && len>1)
len--;
reverse(a+1,a+len+1);
for (int i=1;i<=len;i++)
cout << a[i] << " ";
return 0;
}
版本
import java.util.Scanner;
public class Main {
public static int[] a = new int[200005];
public static int[] b = new int[200005];
public static int[] c = new int[200005];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(), m = scanner.nextInt();
int maxLength = Math.max(n, m);
for (int i = (maxLength - n) + 1; i <= maxLength; ++i)
a[i] = scanner.nextInt();
for (int i = (maxLength - m) + 1; i <= maxLength; ++i)
b[i] = scanner.nextInt();
for (int i = maxLength, cnt = 2; i > 0; --i, ++cnt) {
c[i] += a[i] + b[i];
if (c[i] >= cnt) {
c[i] -= cnt;
c[i - 1] += 1;
}
}
if (c[0] > 0) {
System.out.printf("%d ", c[0]);
}
for (int i = 1; i <= maxLength; ++i) {
System.out.printf("%d ", c[i]);
}
System.out.println();
}
}
C
题解
读入题。暴风吸入输入数据里给定的所有字符,存到数组里,统计有多少个换行符,确定输入文件的总行数
然后就是模拟了。对于第
- 计算出
i 的长度t=\lfloor\lg i+1\rfloor 。 - 输出
s-t 个空格,再输出i ,然后输出1 个空格。 - 从第
i-1 个换行的位置开始,一直到第i 个换行,把中间的字符全部输出。这样第i 行就做完了。
时间复杂度为
参考代码
版本
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
const int MAXN= 2e4 + 3;
char S[MAXN], c; int l, m;
int main(){
m = count(S + 1 , S + 1 + fread(S + 1, 1, MAXN, stdin), '\n');
int s = log10(m) + 1 + 1e-9, p = 0;
up(1, m, i){
int t = log10(i) + 1 + 1e-9;
for(int j = 1;j <= s - t;++ j) putchar( ' '); printf("%d ", i);
for(p = p + 1;S[p] != 10;++ p) putchar(S[p]); putchar('\n');
}
return 0;
}
版本
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
char buf[200050];
vector <char> s[200050];
int cnt;
int get_digit(int x)
{
int digit=1,ret=1;
while (ret<=x)
{
digit++;
ret*=10;
}
return digit;
}
int main()
{
while(fgets(buf,200000,stdin)!=NULL)
{
cnt++;
for (int i=0;buf[i]!='\n';i++)
s[cnt].push_back(buf[i]);
}
int cnt_digit=get_digit(cnt);
for (int i=1;i<=cnt;i++)
{
for (int j=1;j<=cnt_digit-get_digit(i);j++)
putchar(' ');
cout << i << ' ';
for (int j=0;j<s[i].size();j++)
putchar(s[i][j]);
putchar('\n');
}
return 0;
}
版本
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
public class Main {
public static List<String> list = new ArrayList<>();
public static int getBit(int x) {
int cnt = 0;
while (x > 0) {
x /= 10;
++cnt;
}
return cnt;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
list.add(scanner.nextLine());
}
int size = list.size();
int len = getBit(size);
for (int i = 0; i < size; ++i) {
System.out.printf("%" + len + "d %s\n", i + 1, list.get(i));
}
}
}
D
题解
贪心题。
假设
设
断言:所需要的操作次数至少为
证明:如果一个位置在操作后,它的值还在
那么这题怎么做呢?
直接将
时间复杂度为
参考代码
版本
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
int qread(){
int w=1,c,ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
const int MAXN = 1e5 + 3;
int A[MAXN], ans = INF;
int main(){
int n = qread(), m = qread();
up(1, n, i) A[i] = qread();
sort(A + 1, A + 1 + n);
int j = 1;
up(1, min(n, m + 1), i){
j = max(i, j);
while((i - 1) + (n - j) + min(i - 1, n - j) > m) ++ j;
ans = min(ans, A[j] - A[i]);
}
printf("%d\n", ans);
return 0;
}
版本
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static int[] a = new int[100005];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(), m = scanner.nextInt();
for (int i = 1; i <= n; ++i)
a[i] = scanner.nextInt();
Arrays.sort(a, 1, n + 1);
int j = 1, ans = Integer.MAX_VALUE;
for (int i = 1; i <= Math.min(n, m + 1); ++i) {
j = Math.max(j, i);
while((i - 1) + (n - j) + Math.min(i - 1, n - j) > m)
++j;
ans = Math.min(ans, a[j] - a[i]);
}
System.out.println(ans);
}
}
E
题解
数学题。
题目已经贴心地给我们把数列分好了段。容易发现,第
计算出第
分类讨论下输出啥就行了。
时间复杂度为
参考代码
版本
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
i64 qread(){
i64 w=1,c,ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
i64 val(i64 p){return 2 * p * p - p;}
int main(){
up(1, qread(), TTT){
i64 k = qread(), p = 0;
dn(30, 0, i){
if(val(p | 1 << i) < k) p |= 1 << i;
}
int i = p + 1, w = i - 1;
i64 l = val(p) + 1, ll = l + w;
i64 r = val(i), rr = r - w;
if(l <= k && k < ll) printf("%lld\n", k - l ); else
if(ll <= k && k <= rr) printf("%lld\n", w - k + ll); else
printf("%lld\n", k - r);
}
return 0;
}
版本
#include <iostream>
using namespace std;
int main()
{
int q;
cin >> q;
while (q--)
{
long long k,l=1,r=2e9,ans=0;
cin >> k;
while (l<=r)
{
long long mid=(l+r)/2;
if ((long long)mid*mid*2-mid>=k)
{
r=mid-1;
ans=mid;
}
else
l=mid+1;
}
ans--;
long long len=ans*ans*2-ans;
k-=len+1;
if (k<=ans)
cout << k << endl;
else if (k<=3*ans)
cout << 2*ans-k << endl;
else
cout << -4*ans+k << endl;
}
return 0;
}
版本
import java.io.*;
import java.util.StringTokenizer;
public class Main {
public static long val(long p) {
return p * 2 * p - p;
}
public static class Scanner {
public BufferedReader in;
public StringTokenizer tok;
public String next() { hasNext(); return tok.nextToken(); }
public String nextLine() { try { return in.readLine(); } catch (Exception e) { return null; } }
public long nextLong() { return Long.parseLong(next()); }
public int nextInt() { return Integer.parseInt(next()); }
public PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public boolean hasNext() {
while (tok == null || !tok.hasMoreTokens()) try { tok = new StringTokenizer(in.readLine()); } catch (Exception e) { return false; }
return true;
}
public Scanner(InputStream inputStream) { in = new BufferedReader(new InputStreamReader(inputStream)); }
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int q = scanner.nextInt();
while (q-- > 0) {
long k = scanner.nextLong(), p = 0;
for (int i = 30; i >= 0; --i) {
if (val(p | 1 << i) < k)
p |= 1 << i;
}
long i = p + 1, w = i - 1;
long l = val(p) + 1, ll = l + w;
long r = val(i), rr = r - w;
if (l <= k && k < ll)
System.out.println(k - l);
else if (ll <= k && k <= rr)
System.out.println(w - k + ll);
else
System.out.println(k - r);
}
}
}
F
题解
模拟题。没什么好说的。这里就讲几个细节:
- 因为不确定输入数据有多少行,所以要一直读入到没法读入为止。
- 在莲子进行
1 操作前,肯定上一个回合结束了。因此在操作前结算一下每个建筑物给所有者的d_i 资金。此外,在所有操作结束后也要结算一下。这样就能不重不漏地进行q 次结算。 - 每次进行
1 操作时,要依次对经过的每个建筑物进行计算。结算完后检查一下行动者有没有破产。可能不同于常见的大富翁玩法。 - 每次进行
2 操作时,每次都要判断钱够不够、有没有到最高等级、是不是对方的地盘(显然只要不是对方的地盘,那就可以买下/升级),还要判断建造/升级次数有没有达到k 。 - 然后是最后钱数可能爆
\text{int} ,起码得开64 位整型。
时间复杂度为
参考代码
版本
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
int qread(){
int w=1,c,ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
int n, m, q, maxl; bool o = 1;
const int MAXN = 100 + 3;
int C[MAXN][MAXN], A[MAXN], D[MAXN], L[MAXN], O[3], T[MAXN];
i64 M[2];
int main(){
n = qread(), m = qread(), q = qread(), maxl = qread();
M[0] = M[1] = m, O[0] = O[1] = 1;
up(1, n, i) L[i] = 0, T[i] = -1;
up(1, n, i)
up(0, maxl - 1, j) C[i][j] = qread();
up(1, n, i) D[i] = qread();
for(int op, k;~scanf("%d%d", &op, &k);){
if(op == 1){
if(o == 1){
up(1, n, i) if(T[i] != -1) M[T[i]] += D[i];
}
o ^= 1;
up(1, k, i){
O[o] = (O[o]) % n + 1;
int p = O[o];
if(T[p] == o) M[o] += A[p]; else
if(T[p] == !o){
M[!o] += A[p], M[o] -= A[p];
if(M[o] < 0)
puts(o ? "Merry" : "Renko"), exit(0);
}
}
} else {
int p = O[o];
while(k && L[p] < maxl && M[o] >= C[p][L[p]] && T[p] != !o){
A[p] += C[p][L[p]], M[o] -= C[p][L[p]];
++ L[p], T[p] = o, -- k;
}
}
}
up(1, n, i) if(T[i] != -1) M[T[i]] += D[i];
printf("%lld %lld\n", M[0], M[1]);
return 0;
}
版本
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <queue>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,q,L,C[105][105],d[105],a[105],lv[105],pos[2],belong[105];
long long money[2];
const string name[2]={"Renko","Merry"};
inline void putfail(int id)
{
cout << name[id] << endl;
exit(0);
}
inline void forward(int id,int k)
{
for (int i=1;i<=k;i++)
{
int &p=pos[id];
p++;
if (p>n)
p-=n;
if (belong[p]==id)
money[id]+=a[p];
else if (belong[p]==(id^1))
{
money[id]-=a[p];
money[id^1]+=a[p];
}
if (money[id]<0)
putfail(id);
}
}
inline void build(int id,int k)
{
int p=pos[id],cost=C[p][lv[p]];
for (int i=1;(money[id]>=cost && lv[p]<L && (belong[p]==-1 || belong[p]==id) && i<=k);i++)
{
belong[p]=id;
money[id]-=cost;
a[p]+=cost;
cost=C[p][++lv[p]];
}
}
int main()
{
n=read(),m=read(),q=read(),L=read();
for (int i=1;i<=n;i++)
{
for (int j=0;j<L;j++)
C[i][j]=read();
}
for (int i=1;i<=n;i++)
d[i]=read();
pos[0]=pos[1]=1;
fill(belong+1,belong+n+1,-1);
money[0]=money[1]=m;
for (int i=0;i<2*q;i++)
{
int op=read();
begin:
for (int j=1;j<=n && !(i&1);j++)
{
if (belong[j]==0)
money[0]+=d[j];
else if (belong[j]==1)
money[1]+=d[j];
}
int k=read();
forward(i&1,k);
if (i==2*q-1)
break;
op=read();
if (op==1)
{
i++;
goto begin;
}
else
{
k=read();
build(i&1,k);
}
}
for (int j=1;j<=n;j++)
{
if (belong[j]==0)
money[0]+=d[j];
else if (belong[j]==1)
money[1]+=d[j];
}
cout << money[0] << " " << money[1] << endl;
return 0;
}
G
题解
前缀和题。
注意到
这样子有什么好处呢?我们进行一个特殊的二维前缀和。
那比如说
比如,现在需要计算左上角、右下角分别为
更一般地,如果我们希望计算左上角、右下角分别为
现在考察一次询问。
容易发现,我们选取询问矩阵左上角这个
容易计算出小矩阵里的每种颜色,在大矩阵(询问的那个矩阵)里对应的矩阵的左上角、右下角坐标。对于每种颜色,都做一次二维前缀和即可。
时间复杂度为
参考代码
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const int INF = 2147483647;
int n, m, r, c, q;
int qread(){
int w=1,c,ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
const int MAXN = 2e3 + 3;
const int MAXM = 50 + 3;
const int MOD = 998244353;
int A[MAXN][MAXN], S[MAXN][MAXN]; bool B[MAXN][MAXN];
int calc(int a1, int b1, int a2, int b2){
int ret = S[a2][b2];
if(a1 > r) ret = (ret - S[a1 - r][b2] + MOD) % MOD;
if(b1 > c) ret = (ret - S[a2][b1 - c] + MOD) % MOD;
if(a1 > r && b1 > c) ret = (ret + S[a1 - r][b1 - c]) % MOD;
return ret;
}
int main(){
n = qread(), m = qread();
up(1, n, i) up(1, m, j) A[i][j] = qread();
r = qread(), c = qread();
up(1, r, i) up(1, c, j) B[i][j] = qread();
up(1, n, i) up(1, m, j){
S[i][j] = A[i][j];
if(i > r) S[i][j] = (S[i][j] + S[i - r][j]) % MOD;
if(j > c) S[i][j] = (S[i][j] + S[i][j - c]) % MOD;
if(i > r && j > c)
S[i][j] = (S[i][j] - S[i - r][j - c] + MOD) % MOD;
}
q = qread();
up(1, q, i){
int _x1 = qread(), _y1 = qread();
int _x2 = qread(), _y2 = qread();
int ans = 0;
up(1, min(r, _x2 - _x1 + 1), a)
up(1, min(c, _y2 - _y1 + 1), b) if(B[a][b] == 0){
int a1 = _x1 + a - 1, a2 = a1 + (_x2 - a1) / r * r;
int b1 = _y1 + b - 1, b2 = b1 + (_y2 - b1) / c * c;
ans = (ans + calc(a1, b1, a2, b2)) % MOD;
}
printf("%d\n", ans);
}
return 0;
}
H
题解
搜索题。
注意一个重要性质:水流之间可视为互不干扰的。虽然确实有强度更大的水流可以覆盖强度更小的水流这样的设定,但容易发现强度更大的水流,可以流到的空间,包含了强度更小的水流。
(感性理解一下)
于是可以考虑,从高到低计算每个高度有哪些位置是有水流的。下面定义结构体
- 先开一个
\text{map}\lang \text{Pos3},\text{bool}\rang 存一下整个空间里有哪些位置是有实体方块的,记作B 。 - 再开一个
\text{map}\lang \text{Pos2},\text{bool}\rang 存一下当前高度有哪些方块是有水方块的,记作W 。
对于输入进来的每个实体方块
首先将所有实体方块按照
为什么不枚举
现在我们要对
但是可以发现,这一层实际有用的目标位置(紧挨在一个实体方块旁边)是不多的,个数是
当
- 先要开一个
\text{map}\lang \text{Pos2},\text{int}\rang 存一下每一个位置到达目标位置的最短距离,记为D 。 - 还要开一个
\text{map}\lang \text{Pos2},\text{int}\rang 存一下P 里面每个位置它的水方块的强度,记为K 。
接着从
当然,如果
最后是时间复杂度分析。上面出现的三个过程时间复杂度全部都是
参考代码
#include<bits/stdc++.h>
#define up(l,r,i) for(int i=l,END##i=r;i<=END##i;++i)
#define dn(r,l,i) for(int i=r,END##i=l;i>=END##i;--i)
using namespace std;
typedef long long i64;
const int INF =2147483647;
struct Pos2{
int x, y;
Pos2(int _x = 0, int _y = 0):x(_x), y(_y){}
const bool operator < (const Pos2 &t) const {
if(x != t.x) return x < t.x;
return y < t.y;
}
const bool operator > (const Pos2 &t) const {
if(x != t.x) return x > t.x;
return y > t.y;
}
const bool operator ==(const Pos2 &t) const {
return x == t.x && y == t.y;
}
};
struct Pos3{
int x, y, z;
Pos3(int _x = 0, int _y = 0, int _z = 0):
x(_x), y(_y), z(_z){}
const bool operator < (const Pos3 &t) const {
if(x != t.x) return x < t.x;
if(y != t.y) return y < t.y;
return z < t.z;
}
const bool operator > (const Pos3 &t) const {
if(x != t.x) return x > t.x;
if(y != t.y) return y > t.y;
return z > t.z;
}
const bool operator ==(const Pos3 &t) const {
return x == t.x && y == t.y && z == t.z;
}
};
const int BASE = 13331;
struct Hash{
unsigned operator ()(const Pos2 t) const{
return t.x * BASE + t.y;
}
unsigned operator ()(const Pos3 t) const{
return (t.x * BASE + t.y) * BASE + t.z;
}
};
unordered_map<Pos3, bool, Hash> B; // 存 (x, y, z) 是否有方块
unordered_map<Pos2, bool, Hash> V; // 存 (x, y, h + 1) 有没有使用过
unordered_map<Pos2, int , Hash> D; // 存 (x, y) 的最短路程
unordered_map<Pos2, bool, Hash> W; // 存 (x, y, h + 1) 位置有没有水方块
unordered_map<Pos2, int , Hash> K; // 存 (x, y, h + 1) 位置水方块的强度
const int DIR[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
const int MAXN = 2e5 + 3;
int n, p, X[MAXN], Y[MAXN], Z[MAXN], I[MAXN];
int qread(){
int w = 1, c, ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
bool cmp(int a, int b){ return Z[a] > Z[b]; }
int _x0, _y0;
int main(){
n = qread(), p = qread(), _x0 = qread(), _y0 = qread();
W[Pos2(_x0, _y0)] = true;
up(1, n, i){
X[i] = qread(), Y[i] = qread(), Z[i] = qread(), I[i] = i;
B[Pos3(X[i], Y[i], Z[i])] = true;
}
sort(I + 1, I + 1 + n, cmp);
up(1, n, i){
int h = Z[I[i]], j;
queue <Pos2> P, Q;
for(j = i;j <= n && Z[I[j]] == h;++ j){
int o = I[j], x = X[o], y = Y[o];
Pos2 u(x, y);
if(W.count(u))
P.push(u), K[u] = p, W.erase(u);
up(0, 3, k){
int nx = x + DIR[k][0];
int ny = y + DIR[k][1];
Pos2 v(nx, ny);
if(!V.count(v) && !B.count(Pos3(nx, ny, h))
&& !B.count(Pos3(nx, ny, h + 1)))
V[v] = true, D[v] = 0, Q.push(v);
}
}
while(!Q.empty()){
Pos2 u = Q.front(); Q.pop(); int x = u.x, y = u.y;
up(0, 3, k){
int nx = x + DIR[k][0];
int ny = y + DIR[k][1];
Pos2 v(nx, ny);
if(!D.count(v) && B.count(Pos3(nx, ny, h))
&& !B.count(Pos3(nx, ny, h + 1)))
D[v] = D[u] + 1, Q.push(v);
}
}
while(!P.empty()){
Pos2 u = P.front(); P.pop(); int x = u.x, y = u.y;
int d = D[u], s = K[u];
if(!B.count(Pos3{x, y, h})){
W[u] = true; continue;
}
if(s == 1) continue;
up(0, 3, k){
int nx = x + DIR[k][0];
int ny = y + DIR[k][1];
Pos2 v(nx, ny);
if( D[v] == d - 1)
if(!K.count(v) && !B.count(Pos3(nx, ny, h + 1)))
K[v] = s - 1, P.push(v);
}
}
i = j - 1, D.clear(), K.clear(), V.clear();
}
printf("%u\n", W.size());
return 0;
}
参考代码
import java.io.*;
import java.util.*;
public class Main {
public static class Vec2d {
public int x, y;
public Vec2d(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
return Arrays.hashCode(new int[] {x, y});
}
public boolean equals(Vec2d vec2d) {
return this.x == vec2d.x && this.y == vec2d.y;
}
@Override
public boolean equals(Object vec2d) {
if (!(vec2d instanceof Vec2d))
return false;
return this.x == ((Vec2d) vec2d).x && this.y == ((Vec2d) vec2d).y;
}
}
public static class Vec3d {
public int x, y, z;
public Vec3d(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public int hashCode() {
return Arrays.hashCode(new int[] {x, y, z});
}
public boolean equals(Vec3d vec2d) {
return this.x == vec2d.x && this.y == vec2d.y && this.z == vec2d.z;
}
@Override
public boolean equals(Object vec2d) {
if (!(vec2d instanceof Vec3d))
return false;
return this.x == ((Vec3d) vec2d).x && this.y == ((Vec3d) vec2d).y && this.z == ((Vec3d) vec2d).z;
}
}
public static class Scanner {
public BufferedReader in;
public StringTokenizer tok;
public String next() {
hasNext();
return tok.nextToken();
}
public String nextLine() {
try {
return in.readLine();
} catch (Exception e) {
return null;
}
}
public long nextLong() {
return Long.parseLong(next());
}
public int nextInt() {
return Integer.parseInt(next());
}
public PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public boolean hasNext() {
while (tok == null || !tok.hasMoreTokens()) try {
tok = new StringTokenizer(in.readLine());
} catch (Exception e) {
return false;
}
return true;
}
public Scanner(InputStream inputStream) {
in = new BufferedReader(new InputStreamReader(inputStream));
}
}
public static Map<Vec3d, Boolean> isblock = new HashMap<>();
public static Map<Vec2d, Boolean> isused = new HashMap<>();
public static Map<Vec2d, Integer> dist = new HashMap<>();
public static Map<Vec2d, Boolean> iswater = new HashMap<>();
public static Map<Vec2d, Integer> strwater = new HashMap<>();
public static final int[] dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};
public static int n, k, _x0, _y0;
public static int[] x = new int[100050], y = new int[100050], z = new int[100050];
public static List<Integer> var_id = new ArrayList<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
k = scanner.nextInt();
_x0 = scanner.nextInt();
_y0 = scanner.nextInt();
iswater.put(new Vec2d(_x0, _y0), true);
for (int i = 1; i <= n; i++) {
x[i] = scanner.nextInt();
y[i] = scanner.nextInt();
z[i] = scanner.nextInt();
isblock.put(new Vec3d(x[i], y[i], z[i]), true);
var_id.add(i);
}
var_id.sort((x, y) -> z[y] - z[x]);
List<Integer> id = new ArrayList<>();
id.add(0);
for (int i = 0; i < n; ++i)
id.add(var_id.get(i));
for (int i = 0; i < 5; ++i)
id.add(0);
for (int i = 1; i <= n; i++) {
int height = z[id.get(i)];
Queue<Vec2d> p = new LinkedList<>(), q = new LinkedList<>();
// spread at the same height
for (int nid = id.get(i); i <= n && z[nid] == height; ) {
int nx = x[nid], ny = y[nid];
Vec2d u = new Vec2d(nx, ny);
if (iswater.getOrDefault(u, false)) {
iswater.put(u, false);
p.add(u);
strwater.put(u, k);
}
for (int j = 0; j < 4; j++) {
int nx1 = nx + dx[j], ny1 = ny + dy[j];
Vec2d v = new Vec2d(nx1, ny1);
Vec3d v1 = new Vec3d(nx1, ny1, height);
Vec3d v2 = new Vec3d(nx1, ny1, height + 1);
if (!isused.getOrDefault(v, false) && !isblock.getOrDefault(v1, false) && !isblock.getOrDefault(v2, false)) {
isused.put(v, true);
dist.put(v, 0);
q.add(v);
}
}
i++;
nid = id.get(i);
}
i--;
// spread water in Q
while (!q.isEmpty()) {
Vec2d var1 = q.element();
q.remove();
int x = var1.x, y = var1.y;
Vec2d u = new Vec2d(x, y);
for (int j = 0; j < 4; j++) {
int nx = x + dx[j], ny = y + dy[j];
Vec2d v = new Vec2d(nx, ny);
Vec3d v1 = new Vec3d(nx, ny, height);
Vec3d v2 = new Vec3d(nx, ny, height + 1);
if (dist.getOrDefault(v, 0) == 0 && isblock.getOrDefault(v1, false) && !isblock.getOrDefault(v2, false)) {
dist.put(v, dist.get(u) + 1);
q.add(v);
}
}
}
//spread water in P
while (!p.isEmpty()) {
Vec2d var1 = p.element();
p.remove();
int x = var1.x, y = var1.y;
Vec2d u = new Vec2d(x, y);
Vec3d u1 = new Vec3d(x, y, height);
int d = dist.getOrDefault(u, 0), s = strwater.getOrDefault(u, 0);
if (!isblock.getOrDefault(u1, false)) {
iswater.put(u, true);
continue;
}
if (s == 1)
continue;
for (int j = 0; j < 4; j++) {
int nx = x + dx[j], ny = y + dy[j];
Vec2d v = new Vec2d(nx, ny);
Vec3d v1 = new Vec3d(nx, ny, height + 1);
if (dist.getOrDefault(v, 0) == d - 1 && strwater.getOrDefault(v, 0) == 0 && !isblock.getOrDefault(v1, false)) {
strwater.put(v, s - 1);
p.add(v);
}
}
}
isused.clear();
dist.clear();
strwater.clear();
}
int cnt = 0;
for (boolean i : iswater.values()) {
cnt += i ? 1 : 0;
}
System.out.println(cnt);
}
}
I
题解
压轴题。
容易发现这是一棵树。具体可以用归纳法。设前
容易发现这树很特殊。它的点数达到了
定义:我们选出树上一些点作为关键点。这些点包括第
图中标上红星的即为关键点。
断言:关键点的个数是
证明:考虑前
除了关键点,其他所有点的度数均为
把虚树建好后,可以用非常经典的树上最大点权独立集的动态规划做法在虚树上跑。我们关心的是两个关键点之间应该怎么转移。换言之,我们需要知道连接两个关键点的链它的贡献怎么计算。
记
假设有两个关键点
注意一个重要性质:
- 对于每一条连接了两个关键点的链,这条链上的点(不包含顶头的两个关键点),肯定在同一列上。
这同样容易证明。由于该树特殊的构造方法,对于
那么一条链可以被映射到
问题转化为了,查询对
对于
那么怎么对
我们预处理两个东西:
容易预处理上面的两个数组。现在要计算
解释:使用做差的方法计算出
最后讲讲怎么建虚树。维护
于是这题就做完了。
参考代码
#include<bits/stdc++.h>
#define up(l, r, i) for(int i = l, END##i = r;i <= END##i;++ i)
#define dn(r, l, i) for(int i = r, END##i = l;i >= END##i;-- i)
using namespace std;
typedef long long i64;
const i64 INF = 1e18;
int n;
int qread(){
int w=1,c,ret;
while((c = getchar()) > '9' || c < '0') w = (c == '-' ? -1 : 1); ret = c - '0';
while((c = getchar()) >= '0' && c <= '9') ret = ret * 10 + c - '0';
return ret * w;
}
const int MAXN = 1e6 + 3;
const int MAXM = 2e6 + 3;
int R[MAXN], C[MAXN], A[MAXN], F[MAXN], G[MAXM], W[MAXM], o = 0;
int X[MAXM]; i64 U[MAXM][4];
int Y[MAXM]; i64 V[MAXM][4];
int P0[MAXN], Q0[MAXN], P1[MAXN], Q1[MAXN];
int value(int w){return w % 2 == 1 ? w / 2 + 1 : w / 2;}
void calc(int l, int r, i64 &w, bool t){ // [l, r] 区间
if(r - l + 1 == 0){w = 0 ; return;}
if(r - l + 1 == -1){w = 0 ; return;}
if(r - l + 1 == -2){w = -INF; return;}
if(t == false){
int u = min(Q0[l], r); w = P0[r] - P0[u] + ( R[l] == 1 ? value(u - l + 1) : 0);
} else {
int u = min(Q1[l], r); w = P1[r] - P1[u] + (!R[l] == 1 ? value(u - l + 1) : 0);
}
}
void calc(int l, int r, i64 O[4], bool t){ // [l + (0~1), r - (0~1)] 区间
calc(l , r , O[0b11], t);
calc(l , r - 1, O[0b10], t);
calc(l + 1, r , O[0b01], t);
calc(l + 1, r - 1, O[0b00], t);
}
i64 I[MAXM], J[MAXM]; // I 是必须选上,J 是必须不选
void dfs(int u){
if(X[u] == 0) I[u] = W[u], J[u] = 0; else {
int l = X[u], r = Y[u]; dfs(l), dfs(r);
I[u] = W[u]
+ max(U[u][0b00] + I[l], U[u][0b01] + J[l])
+ max(V[u][0b00] + I[r], V[u][0b01] + J[r]);
J[u] =
+ max(U[u][0b10] + I[l], U[u][0b11] + J[l])
+ max(V[u][0b10] + I[r], V[u][0b11] + J[r]);
}
}
int main(){
n = qread();
up(1, n, i) R[i] = qread();
up(1, n, i) C[i] = qread();
up(2, n, i) A[i] = qread();
P0[1] = R[1], P1[1] = !R[1];
up(2, n, i){
P0[i] = max(P0[i - 1], P0[i - 2] + R[i]);
P1[i] = max(P1[i - 1], P1[i - 2] + !R[i]);
}
Q0[n] = Q1[n] = n;
dn(n - 1, 1, i){
if( R[i] == 0) Q0[i] = i; else
if( R[i + 1] == 0) Q0[i] = i; else Q0[i] = Q0[i + 1];
if(!R[i] == 0) Q1[i] = i; else
if(!R[i + 1] == 0) Q1[i] = i; else Q1[i] = Q1[i + 1];
}
up(1, n - 1, i){
int t = A[i + 1], f = F[t]; // (i, t) 是特殊点
F[t] = F[i + 1] = ++ o; // 给特殊点分配编号
if(f)
if(X[f]) Y[f] = o, calc(G[f] + 1, i - 1, V[f], C[t]);
else X[f] = o, calc(G[f] + 1, i - 1, U[f], C[t]);
W[o] = R[i] ^ C[t], G[o] = i;
}
up(1, n, i){ // 最后一层都是特殊点
int t = i, f = F[t]; ++ o, W[o] = R[n] ^ C[i];
if(f)
if(X[f]) Y[f] = o, calc(G[f] + 1, n - 1, V[f], C[t]);
else X[f] = o, calc(G[f] + 1, n - 1, U[f], C[t]);
}
dfs(1); printf("%lld\n", max(I[1], J[1]));
return 0;
}