A - X Axis
暴力枚举一下所有可能
void solve()
{
int a , b , c;
cin >> a >> b >> c;
int ans = 100;
for(int i = 0 ; i <= 10 ; i ++){
ans = min(ans , abs(i - a) + abs(i - b) + abs(i - c));
}
cout << ans << endl;
}
B - Matrix Stabilization
可以观察到:若一个数比周围四个数都大,那么最终会变成四个数当中最大的哪一个。
void solve()
{
int n , m;
cin >> n >> m;
int mp[n][m];
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < m ; j ++){
cin >> mp[i][j];
}
}
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < m ; j ++){
int ma = -1;
if(i > 0){
ma = max(ma , mp[i - 1][j]);
}
if(i + 1 < n){
ma = max(ma , mp[i + 1][j]);
}
if(j > 0){
ma = max(ma , mp[i][j - 1]);
}
if(j + 1 < m){
ma = max(ma , mp[i][j + 1]);
}
cout << min(mp[i][j] , ma) << " ";
}
cout << endl;
}
}
C - Update Queries
对字符串c以及ind数组进行排序,通过贪心可以知道,我们需要按照索引从小到大的修改字符串,同时一个位置只会有一个字母与之对应,因此只需要同时按照字符串c从小到大修改即可。
void solve()
{
int n , m;
cin >> n >> m;
string s;
cin >> s;
int a[m];
map<int,int>mp;
for(int i = 0 ; i < m ; i ++){
cin >> a[i];
mp[a[i]]++;
}
char c[m];
string str;
cin >> str;
for(int i = 0 ; i < m ; i ++){
c[i] = str[i];
}
sort(c , c + m);
int id = 0;
for(auto it : mp){
int x = it.first;
x--;
s[x] = c[id++];
}
for(int i = 0 ; i < n ; i ++){
cout << s[i];
}
cout << endl;
}
D - Mathematical 问题
归纳题,观察后可以发现:一定有一个两位数,因此我们可以枚举这个两位数,然后取最小值。
接下来考虑如何取最小值:若存在数字0,那么可以通过都用乘法来将最终结果变成0,此外,若存在数字1,可以通过乘法将1消掉。除此之外的数,都是加法更加小。按照此策略来模拟即可。
void solve()
{
int n;
cin >> n;
string s;
cin >> s;
int mask[n];
for(int i = 0 ; i < n ; i ++){
mask[i] = s[i] - '0';
}
int ans = 101010;
for(int i = 1 ; i < n ; i ++){
vector<int>tmp;
int tot = 0;
for(int j = 0 ; j < n ; j ++){
if(j == i - 1){
int x = mask[j] * 10 + mask[j + 1];
tmp.pb(x);
j++;
}
else{
tmp.pb(mask[j]);
}
}
for(auto it : tmp){
if(it == 0){
cout << 0 << endl;
return;
}
if(it == 1){
continue;
}
else{
tot += it;
}
}
if(tot == 0) tot++;
ans = min(ans , tot);
}
cout << ans << endl;
}
E - Beautiful Array
思路:我们对所有模k情况下相同的数放到一组,组内的任意两个数都可以通过操作变成相同的数。然后考虑一个组内怎么样才能使得答案最小,显然将相近的两个数放到两边可以将答案变小。因此只需要排序,然后对相邻的两个数两两配对即可。由于需要两两配对,因此若一个组内的数的个数为奇数,除非将一个数放在正中间,否则一定无法满足题意。
若n为奇数,那么需要有一个数放在正中间,也就是需要有一个组内的数的个数为奇数。接下来考虑怎么计算将谁放在中间最合适。假设组内数的个数为,那么需要找出组数来进行配对,考虑将某个位置上的数放到中间后的答案是怎样的:从开头两两配对所组成的组 + 从结尾两两配对所组成的组。因此可以通过一个前缀数组跟一个后缀数组来记录这些值,然后遍历来找到最小值。
void solve()
{
int n , k;
cin >> n >> k;
vector<int>idx[n];
map<int,int>mp;
int id = 0;
set<int>st;
for(int i = 0 ; i < n ; i ++){
cin >> a[i];
int res = a[i] % k;
if(st.count(res)){
int idk = mp[res];
idx[idk].pb(a[i] / k);
}
else{
st.insert(res);
mp[res] = id++;
idx[mp[res]].pb(a[i] / k);
}
}
int ans = 0;
int f = 0;
if(n & 1){
f++;
}
for(int i = 0 ; i < id ; i ++){
sort(idx[i].begin() , idx[i].end());
if(idx[i].size() % 2 == 0){
for(int j = 0 ; j < idx[i].size() ; j += 2){
ans += idx[i][j + 1] - idx[i][j];
}
}
else{
if(f){
int k = idx[i].size() / 2;
vector<int>pre(k + 5 , 0);//总共k个值
int id = 1;
for(int j = 0 ; j + 1 < idx[i].size() ; j += 2){
pre[j / 2 + 1] = pre[j / 2] + idx[i][j + 1] - idx[i][j];
}
vector<int>suf(k + 5 , 0);
for(int j = idx[i].size() - 1 ; j >= 1 ; j -= 2){
suf[j / 2 - 1] = suf[j / 2 ] + idx[i][j] - idx[i][j - 1];
}
int ma = 1e18;
for(int j = 0 ; j <= k ; j ++){
ma = min(ma , pre[j] + suf[j]);
}
ans += ma;
f--;
}
else{
f = -1;
break;
}
}
}
if(!f){
cout << ans << endl;
}
else{
cout << -1 << endl;
}
}
F - Non-academic 问题
可以发现:若存在一个多元环(环上的点大于2),那么无法通过删除一条边来改变他们的连通情况。因此,对于那些多元环而言,我们可以将其缩成一个点。最终:我们通过缩点可以得到一个没有环的连通图,也就是树。接下来只需要通过枚举树上的每条边,将其变成两棵树,然后求出答案的最小值即可。
用无向图的tarjan可以将这些多元环缩成一个点,然后再用树的算法来统计子树的大小跟答案即可。(无向图的tarjan跟有向图的tarjan很像,只需要一条边不连续走两次即可)
// Problem: F. Non-academic Problem
// Contest: Codeforces - Codeforces Round 954 (Div. 3)
// URL: https://codeforces.com/contest/1986/problem/F
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
#define int long long
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
struct SCC {
int n;
std::vector<std::vector<int>> adj;//邻边
std::vector<int> stk;//存储同一个SCC
std::vector<int> dfn, low, bel;//dfn : dfs的时间戳 low : 子树能跳到的最上方的点 bel : 结点位于哪块强连通分量上
int cur, cnt;
SCC() {}
SCC(int n) {
init(n);
}
void init(int n) {
this->n = n;
adj.assign(n, {});
dfn.assign(n, -1);
low.resize(n);
bel.assign(n, -1);
stk.clear();
cur = cnt = 0;
}
void addEdge(int u, int v) {
adj[u].push_back(v);
}
void dfs(int x , int fa) {
dfn[x] = low[x] = cur++;
stk.push_back(x);
for (auto y : adj[x]) {
if(y == fa) continue;
if (dfn[y] == -1) {
dfs(y , x);
low[x] = std::min(low[x], low[y]);
} else if (bel[y] == -1) {
low[x] = std::min(low[x], dfn[y]);
}
}
if (dfn[x] == low[x]) {
int y;
do {
y = stk.back();
bel[y] = cnt;
stk.pop_back();
} while (y != x);
cnt++;
}
}
std::vector<int> work() {
for (int i = 0; i < n; i++) {
if (dfn[i] == -1) {
dfs(i , -1);
}
}
return bel;
}
};
struct HLD {//轻重链剖分
int n;
std::vector<int> siz, top, dep, parent, in, out, seq , val;//子树大小 所在重链的顶部节点 深度 父亲 子树DFS序的起点 子树DFS序的终点
std::vector<std::vector<int>> adj;
int cur = 1;
HLD() {}
HLD(int n) {
init(n);
}
void init(int n) {
this->n = n;
siz.resize(n);
top.resize(n);
dep.resize(n);
parent.resize(n);
in.resize(n);
out.resize(n);
seq.resize(n);
val.assign(n , 0);
cur = 0;
adj.assign(n, {});
}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void work(int root = 1) {
top[root] = root;
dep[root] = 0;
parent[root] = -1;
dfs1(root);
dfs2(root);
}
void dfs1(int u) {
if (parent[u] != -1) {
adj[u].erase(std::find(adj[u].begin(), adj[u].end(), parent[u]));
}
siz[u] = 1;
for (auto &v : adj[u]) {
parent[v] = u;
dep[v] = dep[u] + 1;
dfs1(v);
siz[u] += siz[v];
if (siz[v] > siz[adj[u][0]]) {
std::swap(v, adj[u][0]);
}
}
}
void dfs2(int u) {
in[u] = ++cur;
seq[in[u]] = u;
for (auto v : adj[u]) {
top[v] = v == adj[u][0] ? top[u] : v;
dfs2(v);
val[u] += val[v];
}
out[u] = cur;
}
int lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) {
u = parent[top[u]];
} else {
v = parent[top[v]];
}
}
return dep[u] < dep[v] ? u : v;
}
int dist(int u, int v) {
return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}
int jump(int u, int k) {
if (dep[u] < k) {
return -1;
}
int d = dep[u] - k;
while (dep[top[u]] > d) {
u = parent[top[u]];
}
return seq[in[u] - dep[u] + d];
}
bool isAncester(int u, int v) {//是否为祖先
return in[u] <= in[v] && in[v] < out[u];
}
int rootedParent(int u, int v) {
std::swap(u, v);
if (u == v) {
return u;
}
if (!isAncester(u, v)) {
return parent[u];
}
auto it = std::upper_bound(adj[u].begin(), adj[u].end(), v, [&](int x, int y) {
return in[x] < in[y];
}) - 1;
return *it;
}
int rootedSize(int u, int v) {
if (u == v) {
return n;
}
if (!isAncester(v, u)) {
return siz[v];
}
return n - siz[rootedParent(u, v)];
}
int rootedLca(int a, int b, int c) {
return lca(a, b) ^ lca(b, c) ^ lca(c, a);
}
};
void solve()
{
int n , m;
cin >> n >> m;
SCC scc(n + 5);
pair<int,int>p[m];
for(int i = 0 ; i < m ; i ++){
int u , v;
cin >> u >> v;
scc.addEdge(u , v);
scc.addEdge(v , u);
p[i].x = u;
p[i].y = v;
}
vector<int>v = scc.work();
int ma = -1;
for(int i = 1 ; i <= n ; i ++){
ma = max(ma , v[i]);
}
HLD hld(ma + 5);
for(int i = 0 ; i < m ; i ++){
int u = v[p[i].x] , x = v[p[i].y];
if(u == x) continue;
hld.addEdge(u , x);
}
for(int i = 1 ; i <= n ; i ++){
hld.val[v[i]]++;
}
hld.work();
int ans = n * (n - 1) / 2;
for(int i = 2 ; i <= ma ; i ++){
ans = min(ans , hld.val[i] * (hld.val[i] - 1) / 2 + (n - hld.val[i]) * (n - hld.val[i] - 1) / 2);
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}