Codeforces Round #598 (Div. 3)
A - Payment Without Change
题意:有a个n元硬币和b个1元硬币,求是否能准确表示出S。
题解:1元硬币全部减去,有个范围,其中是否包含n的倍数。这样太复杂了。还是全部尽可能用n支付,然后剩下的用1补齐。
void test_case() {
int a, b, n, S;
scanf("%d%d%d%d", &a, &b, &n, &S);
S -= min(a, S / n) * n;
if(S <= b)
puts("YES");
else
puts("NO");
}
B - Minimize the Permutation
题意:给一个排列,每一对格子至多交换一次,求字典序最小的序列。
题解:每次把最小的尽可能往前冒泡。
int a[105];
int pos[105];
bool vis[105];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
pos[a[i]] = i;
vis[i] = 0;
}
int cur = 1;
do {
for(int i = pos[cur]; i >= 2; --i) {
if(a[i] < a[i - 1] && vis[i] == 0) {
swap(a[i], a[i - 1]);
vis[i] = 1;
} else {
break;
}
}
++cur;
} while(cur <= n);
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i], " \n"[i == n]);
}
C - Platforms Jumping
题意:有m块木板,给出它们的长度,越过长n的河,每次可以移动[1,d]格。不能改变木板的相对顺序,可以移动木板,而木板也不能交叉。
题解:贪心,但是不是从正向贪,因为正向可能会使得木板交叉。先把木板堆在右边,然后需要的时候让他平移到刚刚好能接住的位置,这样保证木板不会交叉的同时自己能跳最远。当预定的跳的地点出现木板时,剩下的就是一串长的木板了。
int c[1005];
int ans[1005], beg[1005];
void test_case() {
int n, m, d;
scanf("%d%d%d", &n, &m, &d);
for(int i = 1; i <= m; ++i)
scanf("%d", &c[i]);
for(int i = m, p = n; i >= 1; --i) {
for(int j = p, k = 1; k <= c[i]; --j, ++k) {
ans[j] = i;
beg[i] = j;
}
p -= c[i];
}
int curn = 0, curm = 0;
while(1) {
/*for(int i = 1; i <= n; ++i)
printf("%d%c", ans[i], " \n"[i == n]);*/
if(curn + d > n || ans[curn + d]) {
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", ans[i], " \n"[i == n]);
return;
} else {
++curm;
if(curm > m) {
puts("NO");
return;
}
for(int i = beg[curm], k = 1; k <= c[curm]; ++i, ++k)
ans[i] = 0;
for(int i = curn + d, k = 1; k <= c[curm]; ++i, ++k) {
ans[i] = curm;
curn = i;
}
}
}
}
D - Binary String Minimizing
题意:给一个01串,使用不超过k次临位交换使得这个串最小。
题解:明显是冒泡排序,感觉是优先把最前面的0冒到最头头。那么记录每个0的位置和头的位置就可以了。
char s[1000005];
int pos[1000005], ptop;
void test_case() {
int n;
ll k;
scanf("%d%lld%s", &n, &k, s + 1);
ptop = 0;
for(int i = 1; i <= n; ++i) {
if(s[i] == '0')
pos[++ptop] = i;
}
if(ptop == n) {
puts(s + 1);
return;
}
int first1 = 1, curp = 1;
while(s[first1] == '0')
++first1, ++curp;
while(k && curp <= ptop) {
if(k >= pos[curp] - first1) {
swap(s[pos[curp]], s[first1]);
k -= pos[curp] - first1;
} else {
swap(s[pos[curp]], s[pos[curp] - k]);
k = 0;
}
++curp;
++first1;
}
puts(s + 1);
}
E - Yet Another Division Into Teams
题意:给n个人,分成若干组,每组至少3个人,每组的极差就是该组的价值,求最小的总价值。
题解:假如丢开数据大小来看,这个随便n^2的dp。
dp[i]=min(dp[j]+a[i]-a[j+1]), for each j<=i-3
移项,看起来就是:
dp[i]=min(dp[j]-a[j+1])+a[i], for each j<=i-3
区间RMQ,写个线段树。
还得复原方案,得记录分割点是从哪里来的,真是麻烦。
struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 200000;
//static const int INF = 0x3f3f3f3f;
int mi[(MAXN << 2) + 5];
int minid[(MAXN << 2) + 5];
void PushUp(int o) {
if(mi[ls] <= mi[rs]) {
mi[o] = mi[ls];
minid[o] = minid[ls];
} else {
mi[o] = mi[rs];
minid[o] = minid[rs];
}
}
void Build(int o, int l, int r) {
if(l == r) {
mi[o] = INF;
minid[o] = l;
} else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
PushUp(o);
}
}
void Update(int o, int l, int r, int ql, int qr, int v) {
if(ql <= l && r <= qr) {
mi[o] = v;
} else {
int m = l + r >> 1;
if(ql <= m)
Update(ls, l, m, ql, qr, v);
if(qr >= m + 1)
Update(rs, m + 1, r, ql, qr, v);
PushUp(o);
}
}
pii QueryMin(int o, int l, int r, int ql, int qr) {
if(ql > qr)
return {INF, -1};
if(ql <= l && r <= qr) {
return {mi[o], minid[o]};
} else {
int m = l + r >> 1;
pii res = {INF, -1};
if(ql <= m) {
pii tmp = QueryMin(ls, l, m, ql, qr);
if(tmp.first < res.first)
res = tmp;
}
if(qr >= m + 1) {
pii tmp = QueryMin(rs, m + 1, r, ql, qr);
if(tmp.first < res.first)
res = tmp;
}
return res;
}
}
#undef ls
#undef rs
} st;
int n;
pii a[200005];
int dp[200005];
int dp2[200005];
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i].first);
a[i].second = i;
}
sort(a + 1, a + 1 + n);
st.Build(1, 1, n);
memset(dp, INF, sizeof(dp[0]) * (n + 1));
dp[0] = 0;
dp[3] = a[3].first - a[1].first;
dp2[3] = 1;
st.Update(1, 1, n, 3, 3, dp[3] - a[4].first);
for(int i = 4; i <= n; ++i) {
pii tmp = st.QueryMin(1, 1, n, 3, i - 3);
dp[i] = tmp.first + a[i].first;
dp2[i] = tmp.second + 1;
if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
dp2[i] = dp2[i - 1];
}
st.Update(1, 1, n, i, i, dp[i] - a[i + 1].first);
}
printf("%d ", dp[n]);
int cur = n, pre = dp2[n], cnt = 1;
while(cur >= 1) {
if(cur < pre) {
++cnt;
pre = dp2[cur];
}
a[cur].first = cnt;
--cur;
}
printf("%d\n", cnt);
for(int i = 1; i <= n; ++i)
swap(a[i].first, a[i].second);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i].second, " \n"[i == n]);
}
再想想这个每次问的区间左端点都固定,改成map不就行了?
map<int, int> st;
int n;
pii a[200005];
int dp[200005];
int dp2[200005];
queue<pii>q;
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i].first);
a[i].second = i;
}
sort(a + 1, a + 1 + n);
memset(dp, INF, sizeof(dp[0]) * (n + 1));
dp[0] = 0;
dp[3] = a[3].first - a[1].first;
dp2[3] = 1;
q.push({dp[3] - a[4].first, 3});
for(int i = 4; i <= n; ++i) {
pii tmp = {INF, -1};
if(st.size())
tmp = *st.begin();
dp[i] = tmp.first + a[i].first;
dp2[i] = tmp.second + 1;
if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
dp2[i] = dp2[i - 1];
}
q.push({dp[i] - a[i + 1].first, i});
if(q.size() >= 3) {
st[q.front().first] = q.front().second;
q.pop();
}
}
printf("%d ", dp[n]);
int cur = n, pre = dp2[n], cnt = 1;
while(cur >= 1) {
if(cur < pre) {
++cnt;
pre = dp2[cur];
}
a[cur].first = cnt;
--cur;
}
printf("%d\n", cnt);
for(int i = 1; i <= n; ++i)
swap(a[i].first, a[i].second);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i].second, " \n"[i == n]);
}
其实都只是取最小,不是单调栈就可以?
int n;
pii a[200005];
int dp[200005];
int dp2[200005];
stack<pii>st;
queue<pii>q;
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i].first);
a[i].second = i;
}
sort(a + 1, a + 1 + n);
memset(dp, INF, sizeof(dp[0]) * (n + 1));
dp[0] = 0;
dp[3] = a[3].first - a[1].first;
dp2[3] = 1;
q.push({dp[3] - a[4].first, 3});
for(int i = 4; i <= n; ++i) {
pii tmp = {INF, -1};
if(st.size())
tmp = st.top();
dp[i] = tmp.first + a[i].first;
dp2[i] = tmp.second + 1;
if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
dp2[i] = dp2[i - 1];
}
q.push({dp[i] - a[i + 1].first, i});
if(q.size() >= 3) {
while(st.size() && st.top().first >= q.front().first)
st.pop();
st.push(q.front());
q.pop();
}
}
printf("%d ", dp[n]);
int cur = n, pre = dp2[n], cnt = 1;
while(cur >= 1) {
if(cur < pre) {
++cnt;
pre = dp2[cur];
}
a[cur].first = cnt;
--cur;
}
printf("%d\n", cnt);
for(int i = 1; i <= n; ++i)
swap(a[i].first, a[i].second);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i].second, " \n"[i == n]);
}
不对,取前缀最小连单调栈都不需要。
int n;
pii a[200005];
int dp[200005];
int dp2[200005];
pii st = {INF, INF};
queue<pii>q;
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i].first);
a[i].second = i;
}
sort(a + 1, a + 1 + n);
memset(dp, INF, sizeof(dp[0]) * (n + 1));
dp[0] = 0;
dp[3] = a[3].first - a[1].first;
dp2[3] = 1;
q.push({dp[3] - a[4].first, 3});
for(int i = 4; i <= n; ++i) {
dp[i] = st.first + a[i].first;
dp2[i] = st.second + 1;
if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
dp2[i] = dp2[i - 1];
}
q.push({dp[i] - a[i + 1].first, i});
if(q.size() >= 3) {
if(q.front().first < st.first)
st = q.front();
q.pop();
}
}
printf("%d ", dp[n]);
int cur = n, pre = dp2[n], cnt = 1;
while(cur >= 1) {
if(cur < pre) {
++cnt;
pre = dp2[cur];
}
a[cur].first = cnt;
--cur;
}
printf("%d\n", cnt);
for(int i = 1; i <= n; ++i)
swap(a[i].first, a[i].second);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i].second, " \n"[i == n]);
}
总结:要先观察DP的式子,然后考虑数据结构的特点。取前缀的可以直接用一个值来维护,转移区间不断右移的可以用单调队列维护,只有当转移区间乱动的时候才需要线段树。在选择线段树的场合,注意维护最小值的坐标的求法。
F - Equalizing Two Strings
题意:有两个等长字符串s,t,每次选择一个长度,把s的这个长度的某区间和t的这个长度的某区间同时翻转,问能否使得s与t相等。
题解:首先排序之后相同才有可能有解,那除此之外什么时候无解呢?设想每次翻转的区间长度都是2,变成一个临位交换,那么一次交换会使得逆序数变化1(冒泡排序),直觉感觉是长的翻转都是分解成这种临位交换的,那么s和t的逆序数会同时变化,所以只需要逆序数的差为偶数,那么可以让逆序数小的那个原地反复交换浪费掉差值。需要注意的是假如有某个字符出现了两次,那么这两个字符在排序之后就可以原地交换浪费掉差值。想不到1A了,说不定这些直觉和乱搞能力已经达到2200了,只是
Graphsforces 要注意学图的知识。
注意归并排序的时候,逆序数应该是longlong,而且只有在弹出j才增加逆序数为剩余的i的数量。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[200005], t[200005], tmp[200005];
ll mergesort(char *s, int l, int r) {
if(l == r)
return 0;
int m = l + r >> 1;
ll res = 0;
res += mergesort(s, l, m);
res += mergesort(s, m + 1, r);
int i = l, j = m + 1, k = 0;
while(i <= m || j <= r) {
if(i > m)
tmp[++k] = s[j++];
else if(j > r)
tmp[++k] = s[i++];
else if(s[i] < s[j])
tmp[++k] = s[i++];
else {
res += m - i + 1;
tmp[++k] = s[j++];
}
}
for(int i = 1; i <= k; ++i)
s[l + i - 1] = tmp[i];
return res;
}
void test_case() {
int n;
scanf("%d%s%s", &n, s + 1, t + 1);
ll rs = mergesort(s, 1, n);
ll rt = mergesort(t, 1, n);
bool suc = 0;
if(strcmp(s + 1, t + 1) == 0) {
int tmp = unique(s + 1, s + 1 + n) - (s + 1);
if(tmp != n || (rs - rt) % 2 == 0)
suc = 1;
}
if(suc)
puts("YES");
else
puts("NO");
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}