C.Cities
题意
给定n个城市,每个城市都有一个形状,你可以使用一次魔法把相同且连续的一段城市变成任意一个形状,问最少使用多少次魔法能使所有城市变成同一个形状。
题解
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5, inf = 0x7f7f7f7f;
typedef long long ll;
int T, n, a[N], la[N], pre[N], dp[N][N];
int main()
{
cin >> T;
while(T--)
{
cin >> n;
memset(pre, 0, sizeof pre);
memset(la, 0, sizeof la);
memset(dp, 0, sizeof dp);
for(int i = 1; i <= n; i++)
{
cin >> a[i];
if(a[i] == a[i - 1]) i--, n--;
}
for(int i = 1; i <= n; i++)
{
pre[i] = la[a[i]];
la[a[i]] = i;
}
for(int len = 2; len <= n; len++)
for(int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
dp[l][r] = dp[l][r - 1] + 1;
for(int k = pre[r]; k >= l; k = pre[k])
dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]);
//cout << l << ' ' << r << "----" << dp[l][r] << endl;
}
cout << dp[1][n] << endl;
}
return 0;
}
J.Parallel Sort
题意
给定从1到n的一个全排列,通过最少轮交换使序列变为升序。
每一轮可以选择多组数对,但两组数对之间不允许有相同的,在这轮中会将这些位置上的数两两交换。并输出每轮交换的次数和要交换的两个数。
题解
每个位置上当前数可能有3种情况:
- 已经在它需要放到的位置上,无需交换。
- 它和它需要放到的位置上的数交换后,两个数都归回原位。
- 它和其他的多个数之间形成了一个环。如图:
只有两个数的小环可以直接在第一轮交换,但是大环则需要进行两轮交换才能全部归位。第一轮可以把大环拆解成多个小环,然后在第二轮直接交换小环中的数即可,因此最多只需要两轮。
交换之后:
这样大环就可以在第一轮交换后拆成小环了,我们设环中数的数量为m,则第一轮交换 ⌈ m 2 ⌉ \left\lceil\dfrac{m}{2}\right\rceil ⌈2m⌉次即可。
代码如下:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<unordered_set>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n, a[N], s[N], flag[N];
//s数组用来记录每个数当前所在位置,flag数组用来标记该数该轮被交换过
struct point
{
int x, y;
};
vector<point> v;//记录需要交换的两个数
vector<int> b[N];//存放每一个大环中的数
void sp(int &x, int &y)//要将该数和其位置都交换
{
swap(s[a[x]], s[a[y]]);
swap(a[x], a[y]);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
s[a[i]] = i;
}
int flag1 = 0, p = 0;
//flag1用来判断有无大环,p用来记下大环的个数
for(int i = 1; i <= n; i++)
if(a[i] != i && !flag[i])
{
int j = s[i], cnt = 0;
if(j == a[i])//如果是小环就直接交换
{
v.push_back({i, j});
flag[i] = flag[j] = 1;
sp(i, j);
continue;
}
flag1 = 1;
b[++p].push_back(i);
while(j != a[i])//找大环并存下来
{
flag[j] = 1;
b[p].push_back(j);
j = s[j];
}
b[p].push_back(j);
flag[j] = 1;
}
if(!flag1)//无大环的情况
{
if(!v.size()) puts("0");//已经是升序了,则无需交换
else
{
puts("1");
int sz = v.size();
cout << sz;
for(int i = 0; i < sz; i++)
cout << ' ' << v[i].x << ' ' << v[i].y;
}
}
else//有大环的情况
{
puts("2");
for(int i = 1; i <= p; i++)
{
int sz = b[i].size();
for(int j = 0, k = sz - 1; j < k; j++, k--)
{
v.push_back({b[i][j], b[i][k]});//将大环第一轮要交换的数存入v数组中
sp(b[i][j], b[i][k]);
}
}
cout << v.size();
for(int i = 0; i < v.size(); i++)
cout << ' ' << v[i].x << ' ' << v[i].y;
cout << endl;
v.clear();//将v数组清空,进行第二轮交换
for(int i = 1; i <= n; i++)
if(a[i] != i)
{
int j = s[i];
v.push_back({i, j});
sp(i, j);
}
cout << v.size();
for(int i = 0; i < v.size(); i++)
cout << ' ' << v[i].x << ' ' << v[i].y;
}
return 0;
}
M.Stone Games
题意
给定一个长度为 n n n的序列,然后进行 q q q次询问,每次询问一个区间中不能用加法凑出的最小的正整数(询问强制在线)。
题解
先说结论:假设该区间所有数为
a
1
≤
a
2
≤
a
3
≤
.
.
.
≤
a
m
a_1\leq a_2\leq a_3\leq... \leq a_m
a1≤a2≤a3≤...≤am,答案用
a
n
s
ans
ans表示。若一个区间中没有
1
1
1,则
a
n
s
=
1
ans=1
ans=1,否则
a
n
s
=
1
+
∑
i
=
1
j
a
i
ans= 1+\sum_{i=1}^{j}a_i
ans=1+∑i=1jai
(
1
≤
j
≤
m
)
(1\leq j\leq m)
(1≤j≤m)。
证明:若区间中没有
1
1
1,很显然
1
1
1就是答案。若有
1
1
1,我们假设用前
i
i
i个数可以凑出
[
1
,
x
]
[1,x]
[1,x],当
a
i
+
1
>
x
+
1
a_{i+1}>x+1
ai+1>x+1时,则有
x
−
a
i
+
1
<
−
1
x-a_{i+1}<-1
x−ai+1<−1,此时用已经凑出的数和
a
i
+
1
a_{i+1}
ai+1无论怎样也凑不出
x
+
1
x+1
x+1,当
a
i
+
1
≤
x
+
1
a_{i+1}\leq x+1
ai+1≤x+1时,则有
x
≥
x
−
a
i
+
1
+
1
≥
0
x\geq x-a_{i+1}+1\geq 0
x≥x−ai+1+1≥0,此时用已经凑出的数和
a
i
+
1
a_{i+1}
ai+1一定能凑出
[
1
,
x
+
a
i
+
1
]
[1,x+a_{i+1}]
[1,x+ai+1]。由此可以推出
a
n
s
=
1
+
∑
i
=
1
j
a
i
ans= 1+\sum_{i=1}^{j}a_i
ans=1+∑i=1jai
(
1
≤
j
≤
m
)
(1\leq j\leq m)
(1≤j≤m)。
a
n
s
ans
ans就是该区间排序后的前缀和加
1
1
1,用主席树来维护区间和即可求出。
代码如下:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
int root[N], n, m, idx;
struct tree
{
int l, r;
ll sum;
}tr[N << 5];
int add(int p, int l, int r, int x)
{
int u = ++idx, mid = l + r >> 1;
tr[u] = tr[p], tr[u].sum += x;
if(l < r)
{
if(x <= mid) tr[u].l = add(tr[p].l, l, mid, x);
else tr[u].r = add(tr[p].r, mid + 1, r, x);
}
return u;
}
ll ask(int p, int u, int l, int r, ll x)
{
if(l == r) return tr[u].sum - tr[p].sum;
ll sum = tr[tr[u].l].sum - tr[tr[p].l].sum, mid = l + r >> 1;
if(x <= mid) return ask(tr[p].l, tr[u].l, l, mid, x);
else return ask(tr[p].r, tr[u].r, mid + 1, r, x) + sum;
}
int main()
{
cin >> n >> m;
int len = 1e9;
for(int i = 1; i <= n; i++)
{
int x;
cin >> x;
root[i] = add(root[i - 1], 1, len, x);
}
ll res = 0;
while(m--)
{
int l, r;
cin >> l >> r;
l = (l + res) % n + 1;
r = (r + res) % n + 1;
if(l > r) swap(l, r);
res = 0;
while(1)
{
ll t = ask(root[l - 1], root[r], 1, len, res + 1); //查询区间排序后的前缀和
if(res == t) break;
res = t;
}
cout << ++res << endl;
}
return 0;
}