A Simple Task
来源:CF11D, 2200
状压 DP.
令 $\mathrm{f[i][x][y]}$ 表示加入的点为 $\mathrm{i}$, 起点为 $\mathrm{x}$, 终点为 $\mathrm{y}$ 的方案.
但是我们可以强制钦定让环中编号最小的点为起点,可以省掉一个 $\mathrm{i}$.
然后转移的时候只可以向末断加点,不可以改变起点,否则会算重.
最后我们对于一个环其实从两个方向都算了一遍,所以要除以 2.
#include <cstdio> #include <cstring> #include <algorithm> #define N 20 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n,m,num[1<<20],is[N][N],LG[1<<N]; ll f[1<<20][20]; int lowbit(int x) { return x & (-x); } int main() { // setIO("input"); scanf("%d%d",&n,&m); for(int i=1;i<=m;++i) { int x,y; scanf("%d%d",&x,&y); is[x][y] = is[y][x] =1 ; } for(int i=1;i<(1<<n);++i) { num[i] = num[i - lowbit(i)] + 1; } for(int i=0;i<n;++i) LG[1 << i] = i; ll ans = 0ll; for(int i=1;i<(1<<n);++i) { for(int j=0;j<n;++j) { if((i&(1<<j)) == 0) continue; if(num[i] == 1) { f[i][j + 1] = 1; } if(num[i] > 2 && is[LG[lowbit(i)] + 1][j + 1]) { ans += f[i][j + 1]; } if(!f[i][j + 1]) continue; for(int nex=0;nex<n;++nex) { if((1 << nex) < lowbit(i)) continue; if(i & (1 << nex)) continue; // 新的,且不能是最小值. if(is[j + 1][nex + 1]) f[i + (1 << nex)][nex + 1] += f[i][j + 1]; } } } printf("%lld\n", ans / 2); return 0; }
Product Sum
来源:CF631E, 2600
先求出不进行操作的答案.
每次考虑 $\mathrm{(i,j)}$ 互换的贡献.
然后退一下式子发现这个贡献是 $\mathrm{a[i]}$ 为 $\mathrm{x}$ 的 $\mathrm{j}$ 提供系数的直线.
这个直线直接用李超线段树维护即可.
由于 $\mathrm{a[i]}$ 的值有负数, 所以在构建线段树时的左端点要注意一下.
#include <cstdio> #include <vector> #include <set> #include <cstring> #include <algorithm> #define N 200009 #define M 2000002 #define ll long long #define pb push_back #define ls now<<1 #define rs now<<1|1 #define setIO(s) freopen(s".in","r",stdin) using namespace std; int a[N], tree[M<<2], n; ll sum[N], suf[N]; struct Line { ll k, b; Line(ll k=0,ll b=0):k(k),b(b){} }line[N]; ll calc(Line o, ll pos) { return o.k * pos + o.b; } void update(int l,int r,int now,int L,int R,int id) { if(l>=L&&r<=R) { int mid = (l + r) >> 1; if(calc(line[tree[now]], mid) < calc(line[id], mid)) swap(id, tree[now]); // 向下传递的是 id. if(l == r) return ; if(calc(line[id], l) > calc(line[tree[now]], l)) update(l, mid, ls, L, R, id); if(calc(line[id], r) > calc(line[tree[now]], r)) update(mid + 1, r, rs, L, R, id); return ; } int mid=(l+r)>>1; if(L<=mid) update(l,mid,ls,L,R,id); if(R>mid) update(mid+1,r,rs,L,R,id); } void build(int l,int r,int now) { tree[now] = 1; if(l == r) return ; int mid = (l + r) >> 1; build(l, mid, ls); build(mid + 1, r, rs); } ll query(int l, int r, int now, int p) { if(l == r) { return calc(line[tree[now]], p); } int mid = (l + r) >> 1; ll cur = calc(line[tree[now]], p); if(p <= mid) return max(cur, query(l, mid, ls, p)); else return max(cur, query(mid + 1, r, rs, p)); } void build2(int l, int r, int now) { tree[now] = n ; if(l == r) return ; int mid=(l+r)>>1; build2(l, mid, ls); build2(mid + 1, r, rs); } int main() { // setIO("input"); scanf("%d",&n); int len = 1000000; // printf("%d\n", n); for(int i=1;i<=n;++i) { scanf("%d",&a[i]); } sum[0] = 0; for(int i=1;i<=n;++i) { sum[i] = sum[i - 1] + a[i]; line[i].k = i; line[i].b = -sum[i - 1]; } build(-len, len, 1); ll fin = 0ll, ans; for(int i = 1; i <= n ; ++ i) { fin += 1ll * i * a[i]; } ans = fin; for(int i = 2; i <= n ; ++ i) { ll det = sum[i - 1] - 1ll * i * a[i] + query(-len, len, 1, a[i]); ans = max(ans, det + fin); update(-len, len, 1, -len, len, i); } for(int i = n; i >= 1; -- i) { suf[i] = suf[i + 1] + a[i]; line[i].k = i; line[i].b = suf[i + 1]; } build2(-len, len, 1); for(int i = n - 1; i >= 1; -- i) { ll det = -suf[i + 1] - 1ll * i * a[i] + query(-len, len, 1, a[i]); ans = max(ans, fin + det); update(-len, len, 1, -len, len, i); } printf("%lld\n", ans); return 0; }
Integer Game
来源:CF1375F, 2600
神仙交互,不看题解根本想不出来.
不妨考虑先手必胜要有怎样局面:
当前 $3$ 个数字构成等差数列且后手在上一轮对当前最大值位置操作了.
不妨在最开始的时候让后手对任意位置加上 $\mathrm{inf}$, 设为 $\mathrm{c}$.
然后第二步的时候只能对于更小的 $\mathrm{a,b}$ 操作了.
这个时候令 $\mathrm{k=2c-a-b}$.
加上之后第二次操作的数字就成了最大值,且相邻两项差为 $\mathrm{c-b}$.
这样,在 $3$ 步之内先手就取得了胜利.
#include <cstdio> #include <cstring> #include <algorithm> #define ll long long using namespace std; ll a[4]; int x[4]; void print(ll x) { printf("%lld\n", x); printf("\n"); fflush(stdout); } int rd() { ll o; scanf("%lld", &o); return (int)o; } int main() { for(int i=1;i<=3;++i) { scanf("%lld",&a[i]); } puts("First"); ll M = 100000000000ll; print(M); x[1] = rd(); // scanf("%lld",&x[1]); a[x[1]] += M; // 下一步:2c - a - b ll d2 = a[x[1]] * 3 - a[1] - a[2] - a[3]; print(d2); x[2] = rd(); // scanf("%d",&x[2]); a[x[2]] += d2; x[3] = 6 - x[1] - x[2]; // x[1]: 最大的. // x[2]: 第二大的. // x[3]: 当前最小. //print(a[x[2]] - a[x[3]]); printf("%lld\n", a[x[1]] - a[x[3]]); return 0; }
Arpa’s overnight party and Mehrdad’s silent entering
来源:CF741C, 2600
直接做没什么思路,看了下题解发现这是个图论题.
显然,对于情侣的配色就是个二分图染色,然后对于其他人也同理.
不妨将 $\mathrm{(2i, 2i-1)}$ 看成一对,然后一对之间连边.
要判断这种连边方式是否有解.
不难发现对于每一个点,最多连 2 条边,所以原图会构成一些链和环.
对于每一种环的情况,也一定是偶数个点,那么就不存在奇环了.
直接连边然后二分图染色就行了.
#include <cstdio> #include <cstring> #include <vector> #include <algorithm> #define N 2000009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n , ans[N], vis[N], A[N], B[N]; vector<int>G[N]; void dfs(int x) { vis[x] = 1; for(int i = 0; i < G[x].size() ; ++ i) { int v = G[x][i]; if(!vis[v]) { ans[v] = ans[x] ^ 1; dfs(v); } } } int main() { // setIO("input"); scanf("%d",&n); for(int i=1;i<=n;++i) { G[2 * i - 1].pb(2 * i); G[2 * i].pb(2 * i - 1); } for(int i=1;i<=n;++i) { int x, y; scanf("%d%d",&x, &y); G[x].pb(y); G[y].pb(x); A[i] = x; B[i] = y; } for(int i = 1; i <= 2 * n ; ++ i) { if(!vis[i]) dfs(i); } for(int i = 1; i <= n ; ++ i) { printf("%d %d\n", ans[A[i]] + 1, ans[B[i]] + 1); } return 0; }
Sonya and Problem Wihtout a Legend
来源:CF713C, 2300
直接弄这个严格递增序列不好搞,不妨考虑严格递增需要满足的条件.
发现,条件为 $\mathrm{i-j\leqslant a[i]-a[j]}$ 对任意 $\mathrm{i,j}$ 都成立.
然后化简一下就是 $\mathrm{a[j]-j \leqslant a[i]-i}$.
令 $\mathrm{c[i]}$ 表示化简后的结果,那只需令 $\mathrm{c[i]}$ 不减就行.
由不减的性质,序列中数字种类肯定不会变.
所以可以设 $\mathrm{f[i][j]}$ 表示前 $\mathrm{i}$ 个数,最后为 $\mathrm{j}$ 的修改次数.
这个东西用 $\mathrm{DP}$ 求一下就好了.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define N 3009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n , cnt ; int a[N], c[N], A[N]; ll f[N][N]; int main() { // setIO("input"); scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d",&a[i]); c[i] = a[i] - i; } cnt = n ; c[++ cnt] = -N; for(int i=1;i<=cnt;++i) A[i]=c[i]; sort(A+1,A+1+cnt); for(int i=1;i<=cnt;++i) c[i]=lower_bound(A+1,A+1+cnt,c[i])-A; memset(f, 0x3f, sizeof(f)); f[0][1] = 0; ll fin = 1000000000000000ll; for(int i=1;i<=n;++i) { ll mi = f[i - 1][1]; for(int j=1;j<=cnt;++j) { mi = min(mi, f[i - 1][j]); f[i][j] = abs(A[c[i]] - A[j]) + mi; if(i == n) fin = min(fin, f[i][j]); } } printf("%lld\n", fin); return 0; }
Make k Equal
来源:CF1328F, 2200
思考最后的形式:
设答案为 $\mathrm{fin}$, 需要大于 $\mathrm{fin}$ 的都变为 $\mathrm{fin+1}$, 然后从中挑选不够的补齐.
对于小于 $\mathrm{fin}$ 的情况同理.
显然,答案只可能为序列中原数,或者相差值为 $1$ 的数字.
直接枚举答案然后利用前缀和算一算就好了.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #include <map> #define N 600009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n,K,cnt; ll suf[N],pre[N]; int a[N],A[N],B[N]; map<int,int>co; int get_pre(int pos) { // 求 <= pos 的个数. int p = lower_bound(B + 1, B + 1 + 1 + n, pos + 1) - B; return p - 1; } int get_aft(int pos) { // 求 >= pos 的个数. return n - get_pre(pos - 1); } int main() { // setIO("input"); scanf("%d%d",&n,&K); for(int i=1;i<=n;++i) { scanf("%d",&a[i]); B[i] = a[i]; co[a[i]] ++ ; if(co[a[i]] >= K) { printf("0\n"); return 0; } A[++cnt] = a[i]; A[++cnt] = a[i] - 1; A[++cnt] = a[i] + 1; } B[n + 1] = 1200000000; sort(B+1, B+1+n); for(int i = n; i >= 1; -- i) suf[i] = suf[i + 1] + 1ll * B[i]; for(int i = 1; i <= n ; ++ i) pre[i] = pre[i - 1] + 1ll * B[i]; sort(A+1, A+1+cnt); ll ans = 10000000000000000ll; for(int i=1;i<=cnt;++i) { // 计算 A[i] 的答案. int num = co[A[i]]; int det = K - num; int prfix = get_pre(A[i] - 1); int sufix = get_aft(A[i] + 1); if(prfix >= det || sufix >= det) { if(prfix >= det) { ll dprfix = 1ll * prfix * (A[i] - 1) - pre[prfix]; ans = min(ans, dprfix + det); } if(sufix >= det) { ll dsufix = suf[n - sufix + 1] - 1ll * sufix * (A[i] + 1); ans = min(ans, dsufix + det); } } else { // 需要都到达目的地. ll dsufix = suf[n - sufix + 1] - 1ll * sufix * (A[i] + 1) ; ll dprfix = 1ll * prfix * (A[i] - 1) - pre[prfix]; ans = min(ans, dsufix + dprfix + det); } } printf("%lld\n", ans); return 0; }