Problem I. 完美回文
思路:
由题意得,肯定是都变成一个字符才满足
暴力枚举
a
−
z
a-z
a−z 求
m
i
n
min
min
code
void solve(){
string s;
cin >> s;
int ans = INF;
for(char i = 'a'; i <= 'z'; i ++){
int res = 0;
for(auto j : s){
if(j != i)
res ++;
}
ans = min(res, ans);
}
cout << ans << endl;
}
Problem G. 邪恶铭刻
思路:
显然,对于op1和op2,执行op2的优先级肯定是最高的
所以按照这个思路去贪心,sum表示攻击力总和,cnt表示怪兽数,初始都为1
1、操作1的时候
s
u
m
+
+
,
c
n
t
+
+
sum++,cnt++
sum++,cnt++
2、操作3的时候计数
k
+
+
k ++
k++
①如果有两只及以上的怪兽肯定是优先合并的
②否则
s
u
m
+
+
,
c
n
t
+
+
sum++,cnt++
sum++,cnt++
3、操作2的时候
遵从能合并就合并,合并不了就把操作3反悔一次
k
−
−
k--
k−−
如果还是不能合并的话,最终答案就是-1
code
const int N = 1e6 + 10;
int sum, cnt, k, st;
int s[N];
int n;
void solve(){
cin >> n;
sum = cnt = 1;
k = st = 0;
rep(i,1,n){
int x; cin >> x;
if(x == 1) sum ++, cnt ++;
else if(x == 0){
if(cnt > 1) k ++, cnt --;
else sum ++, cnt ++;
}
else{
if(cnt > 1) cnt --;
else if(k >= 1) sum ++, cnt ++, k --;
else st = 1;
}
}
if(st) cout << "-1" << endl;
else{
cout << sum / __gcd(sum, cnt) << ' ' << cnt / __gcd(sum, cnt) << endl;
}
}
Problem A. 停停,昨日请不要再重现
思路:
首先,我们假设固定洞不动,去移动整个矩形,那么整个矩形A最大移动范围是
2
∗
n
和
2
∗
m
2*n 和2 * m
2∗n和2∗m设为矩形B
首先我们可以确定图形的移动方式,那么我们反向模拟洞的移动
U:上边界下移最大的行坐标
D:下边界上移最小的行坐标
L:左边界右移最大的列坐标
R:有边界左移最小的列坐标
根据上述定义,只有
(
D
−
U
+
1
)
∗
(
R
−
L
+
1
)
(D-U+1)*(R-L+1)
(D−U+1)∗(R−L+1)范围内的袋鼠会被保留
1、U > D或者R < L
如果
k
k
k 为0,那么每个点都可以是,所以答案是&n*m&
否则,每个点都不可能是,答案是0
2、如果
(
D
−
U
+
1
)
∗
(
R
−
L
+
1
)
(D-U+1)*(R-L+1)
(D−U+1)∗(R−L+1)小于等于
k
k
k那么必定哪个点都不会是
答案为0
3、特判的结束了,接下来是正题
接下来模拟洞的移动,我们假定洞在矩形B的正中央,记为(n+1,m+1),然后模拟图形移动,因为每个点只能计数一次,所以
f
[
x
]
[
y
]
f[x][y]
f[x][y]表示这个点是否有洞经过
然后做一个
f
[
x
]
[
y
]
f[x][y]
f[x][y]的前缀和
f
[
i
]
[
j
]
+
=
f
[
i
−
1
]
[
j
]
+
f
[
i
]
[
j
−
1
]
−
f
[
i
−
1
]
[
j
−
1
]
f[i][j] += f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1]
f[i][j]+=f[i−1][j]+f[i][j−1]−f[i−1][j−1]
假设枚举到(i,j),那么这个矩形中的袋鼠个数为
f
[
x
+
D
]
[
y
+
R
]
−
f
[
U
+
x
−
1
]
[
R
+
y
]
−
f
[
D
+
x
]
[
L
+
y
−
1
]
+
f
[
U
+
x
−
1
]
[
L
+
y
−
1
]
f[x + D][y + R] - f[U + x - 1][R + y] - f[D + x][L + y - 1] + f[U + x - 1][L + y - 1]
f[x+D][y+R]−f[U+x−1][R+y]−f[D+x][L+y−1]+f[U+x−1][L+y−1]
如果这个数字等于k,答案+1
code
#define rep(a,b,c) for(int a=b;a<=c;a++)
const int N = 2e3 + 10;
int f[N][N];
string s;
int n, m, k;
void solve(){
cin >> n >> m >> k >> s;
rep(i, 0, 2 * n + 2)
rep(j, 0, 2 * m + 2)
f[i][j] = 0;
int len = sz(s);
s = " " + s;
int U, D, L, R;
U = 1, D = n, L = 1, R = m;
for(int i = 1, u = 1, d = n, l = 1, r = m; i <= len; i ++){
if(s[i] == 'U') u --, d --;
else if(s[i] == 'R') l ++, r ++;
else if(s[i] == 'L') l --, r --;
else u ++, d ++;
U = max(U, u), D = min(D, d), R = min(R, r), L = max(L, l);
}
if(U > D || R < L){
if(k == 0) cout << n * m << endl;
else cout << 0 << endl;
return;
}
k = (D - U + 1) * (R - L + 1) - k;
if(k < 0){
cout << 0 << endl;
return;
}
for(int i = 1, x = n + 1, y = m + 1; i <= len; i ++){
if(s[i] == 'U') x --;
else if(s[i] == 'R') y ++;
else if(s[i] == 'L') y --;
else x ++;
f[x][y] = 1;
}
f[n + 1][m + 1] = 1;
rep(i,1,2 * n + 1)
rep(j,1,2 * m + 1)
f[i][j] += f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1];
int ans = 0;
rep(i,1,n)
rep(j,1,m){
int x = n + 1 - i, y = m + 1 - j;
int num = f[x + D][y + R] - f[U + x - 1][R + y] - f[D + x][L + y - 1] + f[U + x - 1][L + y - 1];
if(num == k)
ans ++;
}
cout << ans << endl;
}
Problem D. 聊天程序
思路:
二分第
k
k
k 大的最大值,假设这个值是
x
x
x
那么就分为,大于x的部分和小于x的部分,当大于x的个数为k个的时候,那么这个数就是第k大
按照这个思路
我们已经知道x了,然后枚举原序列a
1、
a
i
>
=
x
a_i>=x
ai>=x的时候
s
i
+
1
s_i +1
si+1
2、如果
a
i
a_i
ai加上等差数列的最大值值变成
>
=
x
>=x
>=x 的状态的话,那么就必定有一个
j
j
j,使得
a
i
a_i
ai加上当前等差数列的值恰好
>
x
>x
>x,设这个等差数列的长度为cnt,然后这样就相当于在
m
a
x
(
1
,
i
−
m
+
1
)
max(1, i - m + 1)
max(1,i−m+1)到
m
i
n
(
i
,
i
−
c
n
t
)
+
1
min(i, i - cnt) + 1
min(i,i−cnt)+1的位置上,
i
i
i 提供价值为1的贡献
code
const int N = 2e5 + 10;
ll a[N];
ll n, m, k, c, d;
int check(ll x){
vector<int> s(n + 1, 0);
for(ll i = 1; i <= n; i ++){
if(a[i] >= x)
s[1] ++;
else if(a[i] + c + min(m - 1, i - 1) * d >= x){
s[max(1ll, i - m + 1)] ++;
ll cnt = 0;
if(d != 0)
cnt = (x - a[i] - c + d - 1) / d;
s[min(i, i - cnt) + 1] --;
}
}
for(int i = 1; i <= n - m + 1; i ++){
s[i] += s[i - 1];
if(s[i] >= k)
return 1;
}
return 0;
}
void solve(){
cin >> n >> k >> m >> c >> d;
for(int i = 1; i <= n; i ++)
cin >> a[i];
ll l = 0, r = 1e17;
while(l < r){
ll mid = l + r >> 1;
if(check(mid))
l = mid + 1;
else r = mid;
}
cout << r - 1 << endl;
}
Problem M. 清空水箱
思路:
因为是逆时针给的点坐标,所以可以相连连成一个多边形
1、首先是第一个状态
首先只有①是合法的状态,②④状态可以用to_left测试判断,③状态可以用向量 v 1 v_1 v1的y值判断(y需要为负值)
2、然后是第二个状态
首先①和②都是合法的,而且平台只视为一个点,其余的都是非法的
平台的判定,就是可以假设以平台最右边的点为起点,然后枚举所有的点直到不在一个平面上,然后判断是不是向上的一个向量
code
using point_t=long double; //全局数据类型,可修改为 long long 等
constexpr point_t eps=1e-8;
#define PI acos(-1)
// 点与向量
template<typename T> struct point
{
T x,y;
bool operator==(const point &a) const {return (abs(x-a.x)<=eps && abs(y-a.y)<=eps);}
bool operator<(const point &a) const {if (abs(x-a.x)<=eps) return y<a.y-eps; return x<a.x-eps;}
bool operator>(const point &a) const {return !(*this<a || *this==a);}
point operator+(const point &a) const {return {x+a.x,y+a.y};}
point operator-(const point &a) const {return {x-a.x,y-a.y};}
point operator-() const {return {-x,-y};}
point operator*(const T k) const {return {k*x,k*y};}
point operator/(const T k) const {return {x/k,y/k};}
T operator*(const point &a) const {return x*a.x+y*a.y;} // 点积
T operator^(const point &a) const {return x*a.y-y*a.x;} // 叉积,注意优先级
int toleft(const point &a) const {const auto t=(*this)^a; return (t>eps)-(t<-eps);} // to-left 测试, a是向量,1是左
T len2() const {return (*this)*(*this);} // 向量长度的平方
T dis2(const point &a) const {return (a-(*this)).len2();} // 两点距离的平方
// 涉及浮点数
long double len() const {return sqrtl(len2());} // 向量长度
long double dis(const point &a) const {return sqrtl(dis2(a));} // 两点距离
long double ang(const point &a) const {return acosl(max(-1.0l,min(1.0l,((*this)*a)/(len()*a.len()))));} // 向量夹角
point rot(const long double rad) const {return {x*cos(rad)-y*sin(rad),x*sin(rad)+y*cos(rad)};} // 逆时针旋转(给定角度)
point rot(const long double cosr,const long double sinr) const {return {x*cosr-y*sinr,x*sinr+y*cosr};} // 逆时针旋转(给定角度的正弦与余弦)
};
using Point=point<point_t>;
const int N = 2e3 + 10;
int n;
Point a[N];
void solve(){
cin >> n;
for(int i = 0; i < n; i ++)
cin >> a[i].x >> a[i].y;
int ans = 0;
for(int i = 0; i < n; i ++){
Point x = a[(i - 1 + n) % n], y = a[i], z = a[(i + 1) % n];
Point P = y;
Point v1 = y - x, v2 = z - y;
if(v1.y < 0 && v2.y > 0 && v1.toleft(v2) == 1)
ans ++;
else if(v1.y < 0 && v2.y == 0){
int j = i;
while((++ j %= n) != i && (a[j] - a[(j - 1 + n) % n]).y == 0 && (a[(j + 1) % n] - a[j]).y == 0){
x = a[(j - 1 + n) % n], y = a[j], z = a[(j + 1) % n];
v2 = z - y;
}
if(j != i && (a[(j + 1) % n] - a[j]).y > 0 && (a[j] - P).x > 0)
ans ++;
}
}
cout << ans << endl;
}
Problem B. 索道
思路:
首先可以想到dp,然后想到,我们需要从前缀中代价最小的点转移过来
这个就很容易想到单调队列优化
但是我们发现,在n+1的位置同样有一个点可以对答案有影响
所以同理做一个后缀dp
修改的话,首先如果当前点必选,直接计算就行了,输入
x
x
x 和
r
e
s
res
res
a
n
s
=
p
r
e
[
x
]
+
s
u
f
[
x
]
−
2
∗
a
[
x
]
+
r
e
s
;
ans = pre[x] + suf[x] - 2 * a[x] + res;
ans=pre[x]+suf[x]−2∗a[x]+res;
可以确定一个点的修改,前后最大的影响半径是
k
k
k,所以,我们可以重新选择中间点,这样的话就是当前点向前枚举到
i
i
i,
n
u
m
[
i
]
num[i]
num[i] 表示从
x
到
i
x到i
x到i的最小前缀,然后再向后枚举
i
i
i,答案就是
a
n
s
=
m
i
n
(
a
n
s
,
s
u
f
[
i
]
+
n
u
m
[
m
a
x
(
p
o
s
,
i
−
k
)
]
)
;
ans = min(ans, suf[i] + num[max(pos, i - k)]);
ans=min(ans,suf[i]+num[max(pos,i−k)]);
code
const int N = 5e5 + 10;
ll a[N];
int n, k, q;
ll pre[N], suf[N];
int st[N];
ll num[N];
void solve(){
memset(num, 0x3f, sizeof num);
memset(pre, 0, sizeof pre);
memset(suf, 0, sizeof suf);
memset(st, 0, sizeof st);
cin >> n >> k;
for(int i = 1; i <= n; i ++)
cin >> a[i];
for(int i = 1; i <= n; i ++){
char c; cin >> c;
st[i] = c - '0';
}
deque<ll> de;
for(int i = 1; i <= n; i ++){
while(de.size() && pre[i - 1] < pre[de.back()])
de.pop_back();
de.pb(i - 1);
while(de.size() && i - de[0] > k)
de.pop_front();
if(st[i - 1]){
de.clear();
de.pb(i - 1);
}
pre[i] = pre[de[0]] + a[i];
}
de.clear();
for(int i = n; i >= 1; i --){
while(de.size() && suf[i + 1] < suf[de.back()])
de.pop_back();
de.push_back(i + 1);
while(de.size() && de[0] - i > k)
de.pop_front();
if(st[i + 1]){
de.clear();
de.pb(i + 1);
}
suf[i] = suf[de[0]] + a[i];
}
st[0] = st[n + 1] = 1;
cin >> q;
while(q --){
ll x, res;
cin >> x >> res;
ll ans = pre[x] + suf[x] - 2 * a[x] + res;
if(!st[x]){
num[x] = LNF;
int pos = x;
for(int i = x - 1; x - i < k; i --){
num[i] = min(num[i + 1], pre[i]);
pos = i;
if(st[i]) break;
}
for(int i = x + 1; i - x < k; i ++){
ans = min(ans, suf[i] + num[max(pos, i - k)]);
if(st[i]) break;
}
for(int i = x - 1; x - i < k; i --){
num[i] = LNF;
if(st[i]) break;
}
}
cout << ans << endl;
}
}
Problem E. 树的染色
思路:
先说tag,虚树,然后还需要线段树去维护一个最小代价(或者什么维护都行)
首先,假设当前是操作D
那么对于每个点u,u是v的祖先节点,如果u到v的距离大于D,对于u就毫无意义了,可以直接删掉
这样u可以影响到的点就是与u相距D的点,以此建虚树
在虚树上的状态转移为
res表示当前子树全部染色的最小代价
如果当前节点有儿子节点时
r
e
s
u
=
∑
v
∈
s
o
n
[
u
]
r
e
s
v
res_u = \sum_{v\in son[u]} res_v
resu=∑v∈son[u]resv
否则
r
e
s
=
i
n
f
res=inf
res=inf
r
e
s
=
m
i
n
(
r
e
s
,
q
u
e
r
y
(
1
,
1
,
n
,
D
−
d
[
u
]
+
1
,
D
−
f
a
D
)
)
res = min(res, query(1, 1, n, D - d[u] + 1, D - faD))
res=min(res,query(1,1,n,D−d[u]+1,D−faD))
f
a
D
faD
faD表示父节点的深度,
d
[
u
]
d[u]
d[u]表示当前点
u
u
u的深度
code
const int N = 1e5 + 10;
ll minn[N << 2];
vector<int> g[N];
ll a[N];
int n;
int d[N], f[N][25];
vector<int> vd[N];
int stk[N], tp;
vector<int> vtr[N];
#define ls u << 1
#define rs u << 1 | 1
void build(int u, int l, int r){
if(l == r){
minn[u] = a[l];
return;
}
int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
minn[u] = min(minn[ls], minn[rs]);
}
ll query(int u, int l, int r, int lx, int rx){
if(lx <= l && r <= rx)
return minn[u];
int mid = l + r >> 1;
ll res = LNF;
if(lx <= mid) res = min(res, query(ls, l, mid, lx, rx));
if(rx > mid) res = min(res, query(rs, mid + 1, r, lx, rx));
return res;
}
void dfs(int u, int fa, int dep){
f[u][0] = fa;
for(int i = 1; i <= 24; i ++)
f[u][i] = f[f[u][i - 1]][i - 1];
d[u] = dep;
vd[dep].pb(u);
for(auto v : g[u])
if(v != fa)
dfs(v, u, dep + 1);
}
int lca(int x, int y){
if(d[x] > d[y])
swap(x, y);
for(int i = 24; i >= 0; i --){
if(d[f[y][i]] >= d[x])
y = f[y][i];
}
if(x == y)
return x;
for(int i = 24; i >= 0; i --)
if(f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
ll dp(int u, int faD, int dep){
ll res;
if(!vtr[u].size())
res = LNF;
else{
res = 0;
for(auto v : vtr[u])
res += dp(v, d[u], dep);
}
res = min(res, query(1, 1, n, dep - d[u] + 1, dep - faD));
return res;
}
ll work(int D){
vtr[1].clear();
stk[tp = 1] = 1;
for(auto v : vd[D]){
vtr[v].clear();
int a = lca(v, stk[tp]);
if(a == stk[tp]){
stk[++ tp] = v;
continue;
}
while(d[stk[tp - 1]] > d[a])
vtr[stk[tp - 1]].pb(stk[tp --]);
if(a == stk[tp - 1])
vtr[a].pb(stk[tp --]);
else{
vtr[a].clear();
vtr[a].pb(stk[tp]);
stk[tp] = a;
}
stk[++ tp] = v;
}
while(tp > 1)
vtr[stk[tp - 1]].pb(stk[tp --]);
return dp(1, 0, D);
}
void solve(){
cin >> n;
for(int i = 1; i <= n; i ++)
cin >> a[i];
build(1, 1, n);
for(int i = 1; i < n; i ++){
int u, v;
cin >> u >> v;
g[u].pb(v), g[v].pb(u);
}
dfs(1, 0, 1);
ll ans = a[1];
for(int i = 2; i <= n && vd[i].size(); i ++){
ll k = work(i);
ans += k;
}
cout << ans << endl;
for(int i = 1; i <= n; i ++)
g[i].clear(), vd[i].clear();
}