5.28 考试总结

5.28

T1 欧拉图

  • 看到题并不理解它在说什么,考场上果断弃疗

  • 题目中的定义欧拉图是简单图 · 也就是没有重边和自环

  • n <= 2000, 我们可以考虑 n 方的dp

    • 设 f[i] 代表,i个点之间连边每个点的度数都为偶数的方案数,则 f[i] = (i-1)*(i-2)/2;
      这是因为, 对于i-1个点的任意连边方式,度数为奇数的点的个数一定都为偶数个,我们把这些点与第i个点连边,则所有点度数都为偶数,而对于原本就合法的情况不需要连任何边, 因为没有重边,连任意一条边都会使点的度数变为奇数

    • 上面的 f 数组包含了不连通的情况而对于,我们可以直接容斥掉,设 h[i] 代表欧拉图的方案数,我们可以枚举任何一个点所在的连通块的大小,块之间必须连通,块外可以随便连

    • 所以, h[i] = f[i]-sigma(h[j])*c[i-1][j-1]*f[i-j] (1<=j < i)

#define MAXN 2010UL
#include <cstdio>
#define Mod 1000000007

using namespace std;

int n, f[MAXN], h[MAXN], c[MAXN][MAXN];

int Ksm(int x, int k) {
    int ret = 1;
	while(k) {
		if(k&1) ret = 1ll*ret*x%Mod;
		x = 1ll*x*x%Mod;
		k >>= 1;
	}
	return ret;
}

int main() {
	scanf("%d", &n);
	for(int i = 1 ; i <= n ; ++ i) f[i] = Ksm(2, ((i-1)*(i-2))/2);
	c[0][0] = 1;
	for(int i = 1 ; i <= n ; ++ i) {
		c[i][0] = c[i][i] = 1;
		for(int j = 1 ; j < i ; ++ j) {
			c[i][j] = c[i-1][j-1]+c[i-1][j];
			if(c[i][j]>=Mod) c[i][j] -= Mod;
		}
	}
	for(int i = 1 ; i <= n ; ++ i) {
		h[i] = f[i];
		for(int j = 1 ; j < i ; ++ j) {
			h[i] -= 1ll*(1ll*c[i-1][j-1]*h[j]%Mod)*f[i-j]%Mod;
			if(h[i]<0) h[i] += Mod;
		}
	}
	printf("%d", 1ll*h[n]*((((n-1)*n)/2)+1)%Mod);
	return 0;
}

T2 seq

  • 好丝薄的题目啊,考场上竟然想了三个小时

  • 这道题很难控制的是 max 值,所以我们可以枚举它,找到每个值控制的范围,然后查询,当时并没有想到要从较短的那一端开始扫所以觉得复杂度好玄学,并没有写QAQ,实际上这是O(nlogn)的,考虑一个点每被查询一次,它所在的区间至少扩大两倍,所以每个点最多被查询logn次,在套用主席树的话是log^2的,但我们可以dfs每次找到当前段的最大值从中间劈开,先递归左侧,再递归右侧,把询问存起来这样询问是有序的查询时可以直接O(1)统计,就变成log的了

#define MAXN 300010UL
#include <cstdio>
#include <algorithm>
 
using namespace std;
 
typedef long long ll;
 
int n, K, tot, ret, Rt[MAXN], lg[MAXN], w[MAXN], sum[MAXN], rmq[MAXN][20];
ll ans;
 
struct Seg { int ls, rs, val; } bn[MAXN*20];
 
int Find_max(int l, int r) {
    int len = r-l+1;
    return w[rmq[l][lg[len]]]>w[rmq[r-(1<<lg[len])+1][lg[len]]]?rmq[l][lg[len]]:rmq[r-(1<<lg[len])+1][lg[len]];
}
 
void Insert(int lrt, int &rt, int l, int r, int pos) {
    rt = ++ tot;
    bn[rt] = bn[lrt];
    if(l==r) {
        ++ bn[rt].val;
        return;
    }
    int mid = (l+r)>>1;
    if(pos<=mid) Insert(bn[lrt].ls, bn[rt].ls, l, mid, pos);
    else Insert(bn[lrt].rs, bn[rt].rs, mid+1, r, pos);
    bn[rt].val = bn[bn[rt].ls].val+bn[bn[rt].rs].val;
    return;
}
 
void Find(int lrt, int rt, int l, int r, int pos) {
    if(bn[rt].val-bn[lrt].val<=0) return;
    if(l==r) {
        ret += bn[rt].val-bn[lrt].val;
        return;
    }
    int mid = (l+r)>>1;
    if(pos<=mid) Find(bn[lrt].ls, bn[rt].ls, l, mid, pos);
    else Find(bn[lrt].rs, bn[rt].rs, mid+1, r, pos);
    return;
}
 
void Solve(int l, int r) {
    if(l>=r) return;
    int mid = Find_max(l, r);
    Solve(l, mid-1), Solve(mid+1, r);
    if(r-mid<mid-l) {
        if(l<mid) {
            ret = 0, Find(Rt[l-1], Rt[mid-1], 0, K, (sum[mid]-(w[mid]%K)+K)%K);
            ans += (ll)ret;
        }
        for(int i = mid+1 ; i <= r ; ++ i) {
            ret = 0, Find(Rt[l-1], Rt[mid], 0, K, (sum[i]-(w[mid]%K)+K)%K);
            ans += (ll)ret;
        }
    } else {
        if(mid<r) {
            ret = 0, Find(Rt[mid+1], Rt[r+1], 0, K, (sum[mid-1]+(w[mid]%K))%K);
            ans += (ll)ret;
        }
        for(int i = l ; i < mid ; ++ i) {
            ret = 0, Find(Rt[mid], Rt[r+1], 0, K, (sum[i-1]+(w[mid]%K))%K);
            ans += (ll)ret;
        }
    }
    return;
}
 
int main() {
    scanf("%d%d", &n, &K);
    for(int i = 1 ; i <= n ; ++ i) scanf("%d", &w[i]), sum[i] = (sum[i-1]+(w[i]%K))%K;
    for(int i = 1 ; i <= n ; ++ i) rmq[i][0] = i;
    for(int i = 1, l = 0 ; i <= n ; ++ i) {
        while((1<<l+1)<=i) ++ l;
        lg[i] = l;
    }
    for(int i = 1 ; i <= n+1 ; ++ i) Insert(Rt[i-1], Rt[i], 0, K, sum[i-1]);
    for(int j = 2, t = 1 ; j <= n ; j <<= 1, ++ t) {
        for(int i = 1 ; i+j-1 <= n ; ++ i) {
            rmq[i][t] = w[rmq[i][t-1]]>w[rmq[i+(j/2)][t-1]]?rmq[i][t-1]:rmq[i+(j/2)][t-1];
        }
    }
    Solve(1, n);
    printf("%lld", ans);
    return 0;
}

T3 tree

  • 好神的题目啊(%%%ad大爷)

  • 我们首先考虑一条链的情况,其实就是维护最大连续区间和,用一个线段树就可以了

  • 现在我们拓展到树的情况上,我们还是用线段树维护,但每个点的值为它自己的值加上它所有的虚边连向的儿子的最大前缀和,这样一段区间的和其实就代表了一个连通块的和,修改的时候要改为增量修改,否则还要重新计算 dp 值

  • (好吧,表示偷懒了,原题题解应该极长的)

#define MAXN 100010UL
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define INF 1e9

using namespace std;

int n, m, t, num, op, val1[MAXN], val2[MAXN], tr[MAXN], g[MAXN], w[MAXN], f[MAXN], fa[MAXN], se[MAXN], ch[MAXN], tp[MAXN], st[MAXN], lc[MAXN], rc[MAXN], d[MAXN];
bool fg;

struct Heap {
	priority_queue <int> sv, ts;
	void push(int x) {
		sv.push(x);
		return;
	}
	void pop(int x) {
		ts.push(x);
		return;
	}
	int top() {
		while((!ts.empty())&&ts.top()==sv.top()) ts.pop(), sv.pop();
		return sv.top();
	}
}h;

struct Seg {
	int l, r, mid, s;
	int mx() { return max(max(l, r), mid); }
	friend Seg operator + (Seg a, Seg b) {
		Seg c;
		c.l = max(a.l, a.s+b.l);
		c.r = max(a.r+b.s, b.r);
		c.mid = max(max(a.mid, b.mid), a.r+b.l);
		c.s = a.s+b.s;
		return c;
	}
}bn[MAXN<<2], ans;

struct Edge { int hou, nt; } sg[MAXN<<1];

void Add(int x, int y) {
	sg[t] = (Edge){y, d[x]}, d[x] = t ++;
	sg[t] = (Edge){x, d[y]}, d[y] = t ++;
	return;
}

void Dfs_1(int x, int _fa) {
	fa[x] = _fa, se[x] = 1;
	for(int i = d[x] ; i != -1 ; i = sg[i].nt) {
		if(sg[i].hou==_fa) continue;
		Dfs_1(sg[i].hou, x);
		se[x] += se[sg[i].hou];
		if((!ch[x])||se[ch[x]]<se[sg[i].hou]) ch[x] = sg[i].hou;
	}
	return;
}

void Dfs_2(int x, int _top) {
	tp[x] = _top, st[x] = ++ num, tr[num] = x;
	if(lc[_top]>num) lc[_top] = num;
	if(rc[_top]<num) rc[_top] = num;
	if(ch[x]) Dfs_2(ch[x], _top);
	for(int i = d[x] ; i != -1 ; i = sg[i].nt) {
		if(sg[i].hou==ch[x]||sg[i].hou==fa[x]) continue;
		Dfs_2(sg[i].hou, sg[i].hou);
	}
	return;
}

void Dfs_3(int x) {
	g[x] = w[x];
	for(int i = d[x] ; i != -1 ; i = sg[i].nt) {
		if(sg[i].hou==fa[x]) continue;
		Dfs_3(sg[i].hou);
		if(sg[i].hou!=ch[x]) g[x] += f[sg[i].hou];
	}
	if(g[x]+f[ch[x]]>0) f[x] = g[x]+f[ch[x]];
	return;
}

void Build(int i, int l, int r) {
	if(l==r) {
		bn[i] = (Seg){max(0, g[tr[l]]), max(0, g[tr[l]]), g[tr[l]], g[tr[l]]};
		return;
	}
	int mid = (l+r)>>1;
	Build(i<<1, l, mid), Build(i<<1|1, mid+1, r);
	bn[i] = bn[i<<1]+bn[i<<1|1];
	return;
}

void Find(int i, int l, int r, int ls, int rs) {
	if(ls<=l&&rs>=r) {
		if(!fg) fg = true, ans = bn[i];
		else ans = ans+bn[i];
		return;
	}
	int mid = (l+r)>>1;
	if(ls<=mid) Find(i<<1, l, mid, ls, rs);
	if(rs>mid) Find(i<<1|1, mid+1, r, ls, rs);
	return;
}

void Modify(int i, int l, int r, int pos, int k) {
	if(!k) return;
	if(l==r) {
		bn[i].mid += k, bn[i].s += k;
		bn[i].l = max(bn[i].s, 0), bn[i].r = max(bn[i].s, 0);
		return;
	}
	int mid = (l+r)>>1;
	if(pos<=mid) Modify(i<<1, l, mid, pos, k);
	else Modify(i<<1|1, mid+1, r, pos, k);
	bn[i] = bn[i<<1]+bn[i<<1|1];
	return;
}

void Get_modify(int x, int y) {
	int now_w = y-w[x], now_x = x;
	while(true) {
		h.pop(val1[tp[x]]);
		Modify(1, 1, n, st[x], now_w);
		fg = false, Find(1, 1, n, lc[tp[x]], rc[tp[x]]);
		h.push(val1[tp[x]] = ans.mx());
		now_w = ans.l-val2[tp[x]];
		val2[tp[x]] = ans.l;
		x = fa[tp[x]];
		if(x==0) break;
	}
	w[now_x] = y;
	return;
}

int main() {
	memset(d, -1, sizeof(d));
	int x, y;
	scanf("%d%d", &n, &m);
	for(int i = 1 ; i <= n ; ++ i) scanf("%d", &w[i]), lc[i] = INF;
	for(int i = 2 ; i <= n ; ++ i) scanf("%d%d", &x, &y), Add(x, y);
	Dfs_1(1, 0), Dfs_2(1, 1), Dfs_3(1);
	Build(1, 1, n);
	for(int i = 1 ; i <= n ; ++ i) if(tp[i]==i) {
		fg = false, Find(1, 1, n, lc[i], rc[i]);
		val1[i] = ans.mx(), h.push(val1[i]);
		val2[i] = ans.l;
	}
	for(int i = 1 ; i <= m ; ++ i) {
		scanf("%d", &op);
		if(op==1) scanf("%d%d", &x, &y), Get_modify(x, y);
		else printf("%d\n", h.top());
	}
	return 0;
}
posted @ 2016-05-28 20:54  assassain  阅读(260)  评论(0编辑  收藏  举报