文章目录
A. Floor Tiles
如果是从地板边缘进去的曲线一定组不成一个圆,所以会从另一块地板的边缘离开,又因为每块地板都有 2 2 2 条曲线, n m nm nm 规模的地板总共有 2 ( n + m ) 2(n+m) 2(n+m) 个边缘曲线,所以这些边缘曲线可以组成共 n + m n+m n+m 个曲线,这些曲线都是从边缘进入从边缘离开的。
接下来考虑在地板内部组成的环,直接考虑可以组成最多环的情况。
易证得构造形如 A B B A \begin{array}{c} {A\ B} \\ {B\ A} \end{array} A BB A这样的单位圆是最优的。
如果在最优情况下左上角是 A A A 类型的地板,构造出来的图形就如下图所示,这种情况最多存在 ⌊ ( n − 1 ) ( m − 1 ) + 1 2 ⌋ \lfloor \frac{(n-1)(m-1)+1}2 \rfloor ⌊2(n−1)(m−1)+1⌋ 个环
如果左上角是 B B B 类型的地板,构造出来的图形就如下图所示,这种情况最多存在 ⌊ ( n − 1 ) ( m − 1 ) 2 ⌋ \lfloor \frac{(n-1)(m-1)}2 \rfloor ⌊2(n−1)(m−1)⌋ 个环
(由于比较懒所以直接偷了出题人画的图)
根据给定条件,构造出一个左上角为 A A A 类型或者 B B B 类型地板的 A B AB AB 交替的初始图。如果给定的曲线数量 k k k不在最少曲线数量和最大曲线数量范围中时,输出 N o No No ,否则从上往下,从左往右开始覆盖构造出来的图,每次全部填写 A A A 或者 B B B 来覆盖掉一个构造出来的圆,直到曲线数量满足条件为止,时间复杂度 O ( n m ) O(nm) O(nm)。
#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
char a[1005][1005];
void solve()
{
int n, m, k;
cin >> n >> m >> k;
int x, y, f = 1;
char c;
cin >> x >> y >> c;
x = (x + y) % 2;
int mn = n + m, mx = n + m + (n - 1) * (m - 1) / 2;
if(x == 0 && c == 'A' || x == 1 && c == 'B')
{
f = 0;
mx = n + m + ((n - 1) * (m - 1) + 1) / 2;
}
if(k < mn || k > mx)
{
cout << "No" << '\n';
return;
}
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
if((i + j) % 2 == f) a[i][j] = 'A';
else a[i][j] = 'B';
}
}
for(int i = 1;i < n;i++)
{
for(int j = 1;j < m;j++)
{
if(mx > k && (i + j) % 2 == f)
{
mx--;
a[i][j] = a[i][j + 1] = c;
}
}
}
cout << "Yes" << '\n';
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++) cout << a[i][j];
cout << '\n';
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
B. MST
由于查询点数量有限,且边的总数有限,所以我们将数据进行分块处理。
当询问点数量 ≤ n \leq \sqrt{n} ≤n 时用两个 f o r for for 循环暴力查找 m a p map map 中是否存有这两个点的边,如果有加入当前的边集。当询问点数量 > n > \sqrt{n} >n 时访问所有存在的 m m m 条边,查询是否有这条边对应的两个点,如果有加入当前的边集。
处理完边集后用Kruskal最小生成树算法即可,时间复杂度 O ( n l o g n + m n ) O(nlogn+m\sqrt{n}) O(nlogn+mn)。
#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
struct node
{
int u, v, w;
};
vector<node> e, ne;
int fa[N], b[N];
int find(int x) {
while (x != fa[x]) {
x = fa[x] = fa[fa[x]];
}
return x;
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
fa[y] = x;
return true;
}
bool cmp(node a, node b)
{
return a.w < b.w;
}
void solve()
{
map<pii, int> mp;
int n, m, q;
cin >> n >> m >> q;
int T = sqrt(n);
for(int i = 1;i <= m;i++)
{
int u, v, w;
cin >> u >> v >> w;
if(u > v) swap(u, v);
e.push_back({u, v, w});
mp[{u, v}] = w;
}
while(q--)
{
int k;
cin >> k;
for(int i = 1;i <= k;i++)
{
cin >> b[i];
fa[b[i]] = b[i];
}
if(k <= T)
{
for(int i = 1;i <= k;i++)
{
for(int j = i + 1;j <= k;j++)
{
int u = b[i], v = b[j];
if(u > v) swap(u, v);
if(mp.count({u, v})) ne.push_back({u, v, mp[{u, v}]});
}
}
}
else
{
for(auto [u, v, w] : e)
{
if(fa[u] && fa[v]) ne.push_back({u, v, w});
}
}
sort(all(ne), cmp);
int ans = 0, cnt = 0;
for(auto [u, v, w] : ne)
{
if(merge(u, v))
{
cnt++;
ans += w;
}
}
if(cnt == k - 1) cout << ans << '\n';
else cout << -1 << '\n';
for(int i = 1;i <= k;i++) fa[b[i]] = 0;
ne.clear();
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
//cin >> t;
while (t--)
solve();
return 0;
}
C. Red Walking on Grid
考虑 d p [ i ] [ j ] dp[i][j] dp[i][j] , i i i 为当前的横坐标 j j j 为当前的纵坐标,如果当前行是 R R R 则可以从 d p [ i ] [ j − 1 ] dp[i][j - 1] dp[i][j−1] 转移而来,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + 1 dp[i][j]=dp[i-1][j]+1 dp[i][j]=dp[i−1][j]+1 ,如果当前行和另一行均为 R R R ,则可以从另一行转移而来,即 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ 1 − j ] + 2 ) dp[i][j] = max(dp[i][j],dp[i - 1][1-j]+2) dp[i][j]=max(dp[i][j],dp[i−1][1−j]+2) ,每次计算完 d p dp dp 值后 a n s ans ans 取 m a x ( a n s , m a x ( d p [ i ] [ 0 ] , d p [ i ] [ 1 ] ) ) max(ans,max(dp[i][0],dp[i][1])) max(ans,max(dp[i][0],dp[i][1])) ,最后的 a n s − 1 ans-1 ans−1 即为所求,时间复杂度 O ( n ) O(n) O(n) 。
#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6+ 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
int dp[N][2];
void solve()
{
int n;
cin >> n;
string a, b;
cin >> a;
cin >> b;
a = " " + a;
b = " " + b;
int ans = 1;
for(int i = 1;i <= n;i++)
{
if(a[i] == 'R') dp[i][0] = dp[i - 1][0] + 1;
if(b[i] == 'R') dp[i][1] = dp[i - 1][1] + 1;
if(a[i] == 'R' && b[i] == 'R')
{
dp[i][0] = max(dp[i][0], dp[i - 1][1] + 2);
dp[i][1] = max(dp[i][1], dp[i - 1][0] + 2);
}
ans = max(ans, max(dp[i][0], dp[i][1]));
}
cout << ans - 1 << '\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
//cin >> t;
while (t--)
solve();
return 0;
}
E. GCD VS XOR
将 n n n 的二进制的最低位 1 1 1 改成 0 0 0 后的数即为所求,如果 n n n 是 2 2 2 的整数次幂则无解,时间复杂度 O ( 64 t ) O(64t) O(64t)。
#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 10;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
void solve()
{
int n;
cin >> n;
int cnt = 0, ans = INF;
for(int i = 0;i <= 63;i++)
{
if((1LL << i) & n)
{
cnt++;
ans = min(ans, i);
}
}
if(cnt == 1) cout << -1 << '\n';
else cout << n - (1LL << ans) << '\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
H. Instructions Substring
特判:如果目标坐标为 ( 0 , 0 ) (0,0) (0,0) 直接输出 n ∗ ( n + 1 ) / 2 n*(n +1)/2 n∗(n+1)/2 。
设当前位置 n x = 0 nx=0 nx=0 , n y = 0 ny=0 ny=0 ,目标位置为 ( x , y ) (x,y) (x,y) 从前往后遍历字符串,遇到相应的指令改变 n x nx nx 和 n y ny ny 的值,并将 n x nx nx 和 n y ny ny 的值存在 m a p map map 中,同时进行查询,如果 m a p map map 中存在元素 n x − x , n y − y {nx-x,ny-y} nx−x,ny−y,说明前面存在若干个左端点,使得运行完这些左端点指令到当前指令后刚好可以到达目标位置,所以以这些左端点为左端点,后面的所有指令子串都一定能经过目标位置,查询完后将 m p [ n x − x , n y − y ] mp[{nx-x,ny-y}] mp[nx−x,ny−y] 的值置为 0 0 0,累加后的答案即为所求,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6 + 10;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
void solve()
{
int n, x, y;
cin >> n >> x >> y;
string s;
cin >> s;
if(x == 0 && y == 0)
{
cout << n * (n + 1) / 2 << '\n';
return;
}
int nx = 0, ny = 0, ans = 0;
map<pii, int> mp;
mp[{0, 0}] = 1;
for(int i = 0;i < n;i++)
{
if(s[i] == 'W') ny++;
else if(s[i] == 'S') ny--;
else if(s[i] == 'A') nx--;
else nx++;
mp[{nx, ny}]++;
//cout << nx << " " << ny << '\n';
if(mp.count({nx - x, ny - y}))
{
ans = ans + mp[{nx - x, ny - y}] * (n - i);
mp[{nx - x, ny - y}] = 0;
}
//cout << i << " " << ans << '\n';
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
//cin >> t;
while (t--)
solve();
return 0;
}
I. Red Playing Cards
用 f ( i ) f(i) f(i) 表示拿掉 ( l [ i ] , r [ i ] ) (l[i],r[i]) (l[i],r[i]) 区间后能获得的最大分数,如果在初始序列的左右两端都加一个 0 0 0 后是不影响最后的答案的,所以 f ( 0 ) f(0) f(0) 即为所求。
对于每个 i i i ,遍历 ( l [ i ] , r [ i ] ) (l[i],r[i]) (l[i],r[i]) ,如果拿走 ( l [ i ] , r [ i ] ) (l[i],r[i]) (l[i],r[i]) 区间,对于每个下标 j j j 来说都为 f ( i ) f(i) f(i) 贡献了 i i i ,所以可以得到转移方程 d p [ j + 1 ] = m a x ( d p [ j + 1 ] , d p [ j ] + i ) dp[j+1]=max(dp[j+1],dp[j]+i) dp[j+1]=max(dp[j+1],dp[j]+i) 。如果当前下标 j j j 满足 l [ a [ j ] ] = = j & & r [ a [ j ] ] < r [ i ] l[a[j]]==j\&\&r[a[j]]<r[i] l[a[j]]==j&&r[a[j]]<r[i] ,此时就有两种选择,一种是先拿走 ( l [ a [ j ] ] , r [ a [ j ] ] ) (l[a[j]],r[a[j]]) (l[a[j]],r[a[j]]) 区间,另一种是保留 ( l [ a [ j ] ] , r [ a [ j ] ] ) (l[a[j]],r[a[j]]) (l[a[j]],r[a[j]]) 区间,由此就能得出转移方程 d p [ r [ a [ j ] ] + 1 ] = m a x ( d p [ r [ a [ j ] ] + 1 ] , d p [ j ] + f [ a [ j ] ] ) dp[r[a[j]] + 1] = max(dp[r[a[j]] + 1], dp[j] + f[a[j]]) dp[r[a[j]]+1]=max(dp[r[a[j]]+1],dp[j]+f[a[j]]),由于遍历的时候没有计算 l [ i ] l[i] l[i] 和 r [ i ] r[i] r[i] 两个端点,所以 f [ i ] f[i] f[i] 要加上 2 ∗ i 2*i 2∗i ,即 f [ i ] = 2 ∗ i + d p [ r [ i ] ] f[i]=2*i+dp[r[i]] f[i]=2∗i+dp[r[i]],最终输出 f [ 0 ] f[0] f[0] 即可,时间复杂度 O ( n 2 ) O(n^2) O(n2)。
#include <bits/stdc++.h>
#define int long long
#define YES "YES"
#define NO "NO"
#define all(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const double eps = 1e-9;
const int N = 1e6+ 100;
const int INF = 1e18 + 100;
const int mod = 998244353;
const int base = 23333;
int a[N], l[N], r[N];
void solve()
{
int n;
cin >> n;
for(int i = 1;i <= 2 * n;i++)
{
cin >> a[i];
r[a[i]] = i;
}
for(int i = 2 * n;i >= 1;i--) l[a[i]] = i;
l[0] = 0;
r[0] = 2 * n + 1;
vector<int> f(n + 5, 0);
for(int i = n;i >= 0;i--)
{
vector<int> dp(2 * n + 5, 0);
for(int j = l[i] + 1;j < r[i];j++)
{
dp[j + 1] = max(dp[j + 1], dp[j] + i);
if(j == l[a[j]] && r[a[j]] < r[i]) dp[r[a[j]] + 1] = max(dp[r[a[j]] + 1], dp[j] + f[a[j]]);
}
f[i] = 2 * i + dp[r[i]];
}
cout << f[0] << '\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
//cin >> t;
while (t--)
solve();
return 0;
}