我永远讨厌blutrex的题目靴靴
这道题就是一道裸裸欧拉回路 但是由于我之前并没有写过...。 真的难受QAQ
这道题首先想到建图是对于每一个简单词看作图上的边 某单词$ab$就建边$a -> b$ 如果单词可以反转再建一条反向边即可
那么题目就转化成了给定若干边 求这个图是否可以"一笔画" 也就是说可不可以一遍$dfs$就可以跑完所有的边
这是裸欧拉回路啊 所以这道题分为无向图和有向图两种
1.有向图
欧拉回路有向图首先要看他是否成环 有两种情况
一个是有固定的起点终点 起点的出度比入度多一 因为他不会回来 终点的出度比入度少一 因为最后会停留在这个点
二是没有固定起点终点 那么这个时候每个点入度等于出度
2.无向图
欧拉回路无向图和又想吐是类似的
有固定起点终点 这个时候起点终点的度数均为奇数 这两个点可以是起点终点or终点起点
没有固定... 这个时候判断度数均为偶数即可
那么最后还要判断是否可以跑完所有边 跑的时候把路径储存起来即可
代码
#include <bits/stdc++.h> using namespace std; const int N = 8e5 + 5; int deg[N], tot = 1, head[N], tov[2 * N], nex[2 * N], id[2 * N]; int stk[2 * N], top, n, m, in[N], out[N], T; bool vis[N]; void add(int u, int v, int x) { tot ++; nex[tot] = head[u]; tov[tot] = v; head[u] = tot; id[tot] = x; } void dfs1(int u, int fre) { for(int i = head[u]; i; i = nex[i]) { int v = tov[i]; if(vis[i]) continue; vis[i] = vis[i ^ 1] = true; dfs1(v, i); } stk[++ top] = id[fre]; } int solve1( ) { int cnt = 0, st; scanf("%d%d",& n,& m); for(int i = 1;i <= m;i ++) { int u, v; scanf("%d%d",& u,& v); st = u; add(u, v, i); add(v, u, -i); deg[u] ++; deg[v] ++; } for(int i = 1;i <= n;i ++) { if(deg[i] & 1) cnt ++, st = i; } if(cnt > 2 || cnt == 1) return !puts("NO"); dfs1(st, 0); if(top != m + 1) return !puts("NO"); puts("YES"); for(int i = top - 1; i; i --) { printf("%d ", stk[i]); } puts(""); } void dfs2(int u, int fre) { for(int i = head[u]; i; i = nex[i]) { int v = tov[i]; if(vis[i]) continue; vis[i] = true; dfs2(v, i); } stk[++ top] = id[fre]; } int solve2( ) { int cnt = 0, st = -1, ed = -1, U; scanf("%d%d",& n,& m); for(int i = 1; i <= m; i ++) { int u, v; scanf("%d%d",& u,& v); U = u; add(u, v, i); in[v] ++; out[u] ++; } for(int i = 1; i <= n; i ++) if(in[i] != out[i]) { cnt ++; if(in[i] == out[i] + 1) ed = i; if(in[i] + 1 == out[i]) st = i; } if((cnt != 0 && cnt != 2) || ((st == -1|| ed == -1) && cnt)) return !puts("NO"); if(st == -1) st = U; dfs2(st, 0); if(top != m + 1) return !puts("NO"); puts("YES"); for(int i = top - 1;i >= 1;i --) { printf("%d ", stk[i]); } puts(""); } int main( ) { freopen("merge.in", "r", stdin); freopen("merge.out", "w", stdout); scanf("%d",& T); if(T == 1) solve1( ); else solve2( ); }
对不起第二题我真的不会:))
我觉得这道题是因为考试的时候有点不想想第三题了然后就gg了 其实还是比较简单的,, 后悔sl
我们用$dp[i]$表示初始以位置$i$作为逆序对靠前元素的位置的逆序对个数 这个东西可以用树状数组处理出来
比如$1,5,4,2,6,3$那么$dp[3] = 2(4,2),(4,3) , dp[2] = 3(5, 4),(5, 2),(5, 3)...$
我们可以推出一个很明显的性质
首先对于修改位置$i$的$a[i]$ 我们可以发现修改之后被重新排过的数的$dp$值变为$0$其余的数不变
证明:
因为他被重新排过 所以$i$后小于等于$a[i]$的数都是递增排列的 并且若他被排 那么比它更小的数也一定被排 且排在他的前面 至于没被排的数他们本来就不会和他产生逆序对 所以被排列的数的$dp$变为$0$
至于没有被排的数 他们本来就比排的数要大 那么不管这些小的数如何排列 那么他们仍旧比他小 仍然构成逆序对且数量不变
至于在$i$前的数 显然不会造成影响 因为前面的数与后面的不管是什么数相对位置都没有变化
所以考虑每次修改过后在总的逆序对中减去他的$dp$值 再把被排列的数$dp$修改为$0$ 即可 这个可以使用链表维护(其实链表可以被卡 但是没有卡)
至于更优秀的做法是使用线段树维护区间最小值 每次修改就在他后面的区间不断查询最小值修改 修改后把他自己删去 直到最小值大于当前值即可
代码(链表)
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 5; int c[N], n, a[N], m, dp[N], nex[N], pre[N]; long long sum; int lowbit(int x) {return x & (-x);} int query(int pos) { int ans = 0; while(pos >= 1) { ans += c[pos]; pos -= lowbit(pos); } return ans; } void add(int pos) { while(pos <= n) { c[pos] ++; pos += lowbit(pos); } } void Init( ) { scanf("%d%d",& n,& m); for(int i = 1; i <= n; i ++) scanf("%d",& a[i]); for(int i = n; i >= 1; i --) { dp[i] = query(a[i] - 1); add(a[i]); sum += dp[i]; } for(int i = 1;i <= n;i ++) nex[i] = i + 1, pre[i] = i - 1; } void Solve( ) { printf("%lld ", sum); for(int i = 1;i <= m;i ++) { int x; scanf("%d",& x); if(! dp[x]) {printf("%lld ", sum); continue;} for(int j = x;j <= n;j = nex[j]) if(a[j] <= a[x]) nex[pre[j]] = nex[j], pre[nex[j]] = pre[j], sum -= dp[j], dp[j] = 0; printf("%lld ",sum); } } int main( ) { freopen("count.in", "r", stdin ) ; freopen("count.out", "w", stdout ) ; Init( ); Solve( ); }
代码(线段树)
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define ll long long using namespace std; const ll maxn = 2e5 + 5; ll n, m, ans = 0; ll read() { ll rt = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { rt = (rt << 1) + (rt << 3) + ch - '0'; ch = getchar(); } return rt * f; } struct Tree { ll vmin, pos; }t[maxn << 2]; ll a[maxn], j[maxn], f[maxn], cnt[maxn], p[maxn], v[maxn], num; ll lowbit(ll x) { return x & (-x); } ll query(ll pos) { ll rt = 0; for (int i = pos; i >= 1; i-= lowbit(i)) { rt += f[i]; } return rt; } void modify(ll pos, ll val) { for (int i = pos; i <= n; i += lowbit(i)) { f[i] += val; } } void update(ll o) { if (t[o << 1].vmin < t[o << 1 | 1].vmin) { t[o] = t[o << 1]; } else { t[o] = t[o << 1 | 1]; } } void build(ll o, ll lf, ll rg) { if (lf == rg) { t[o].pos = lf; t[o].vmin = a[lf]; return ; } ll mid = (lf + rg) >> 1; build(o << 1, lf, mid); build(o << 1 | 1, mid + 1, rg); update(o); } void modify(ll o, ll lf, ll rg, ll pos, ll val) { if (lf == rg) { t[o].vmin = val; return ; } ll mid = (lf + rg) >> 1; if (pos <= mid) { modify(o << 1, lf, mid, pos, val); } else { modify(o << 1 | 1, mid + 1, rg, pos, val); } update(o); } Tree query(ll o, ll lf, ll rg, const ll L, const ll R) { if (L <= lf && rg <= R) { return t[o]; } ll mid = (lf + rg) >> 1; Tree rt1, rt2; bool flag1 = 0, flag2 = 0; if (L <= mid) { flag1 = 1; rt1 = query(o << 1, lf, mid, L, R); } if (R > mid) { flag2 = 1; rt2 = query(o << 1 | 1, mid + 1, rg, L, R); } if (flag1 && flag2) { if (rt1.vmin < rt2.vmin) return rt1; else return rt2; } else if (flag1 && !flag2) { return rt1; } else if (!flag1 && flag2) { return rt2; } } int main() { freopen("count.in","r",stdin); freopen("count.out","w",stdout); n = read(), m = read(); for (int i = 1; i <= n; i++) { a[i] = read(); } for (int i = 1; i <= m; i++) { j[i] = read(); } for (int i = n; i >= 1; i--) { ll now = query(a[i] - 1); cnt[i] = now; ans += cnt[i]; modify(a[i], 1); } build(1, 1, n); printf("%lld ",ans); for (int i = 1; i <= m; i++) { num = 0; while (1) { Tree now = query(1, 1, n, j[i], n); if (now.vmin > a[j[i]]) break; num += cnt[now.pos]; cnt[now.pos] = 0; modify(1, 1, n, now.pos, 1e9); } ans -= num; printf("%lld", ans); if (i != m) { printf(" "); } } return 0; }