CCPC-Wannafly Winter Camp Day5(div1 + div2 部分题解)

啊,来了五天(今天应该是第六天了)camp了,终于可以愉快的补题了,由于前面欠下的题目好像有点多,所以只能从后往前将题目以及题解补上了(希望能在有生之年把能补的题目补完吧,QAQ)。

第五天是dls场,dls对待菜鸡还是非常友好的,div2的题目简直是快乐无比,div1的就不快乐了,但还是得把能补的题目补一补。

 

A.Cactus Draw

div1版本:给你一棵仙人掌,要将所有节点放到二维平面中,同时使得所有边都没有交点,输出构造方案。(暂时不会,待补)

div2版本:给一棵树,其他同div1一样。

简易题解:如果是一棵树,这个题目就是一个非常快乐的题目了,只需要dfs一遍,计算一下每个节点的深度,然后按深度从上往下放,深度相同的从左往右放就可以了。

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"

typedef double db;
typedef long long ll;
typedef pairpii;
typedef pairpll;
const db eps = 1e-9;
const db pi = acos(-1.0);
const int MX = 1000 + 5;
//head

int n,m;
int mx = 0;
vectorG[MX],dep[MX];

void dfs(int u,int fa,int d){
    dep[d].pb(u);
    mx = max(mx,d);
    for(auto v : G[u]){
        if(v == fa) continue;
        dfs(v,u,d+1);
    }
}

pii ans[MX];

int main(){
#ifdef LOCAL
    FIN;
#endif
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].pb(v);G[v].pb(u);
    }
    dfs(1,0,1);
    for(int i = 1;i <= mx;i++){
        for(int j = 0;j < dep[i].size();j++)
            ans[dep[i][j]] = MP(i,j+1);
    }
    for(int i = 1;i <= n;i++)
        printf("%d %d\n",ans[i].fi,ans[i].se);
    return 0;
}

B.Diameter(待补)

div1版本:所有n个节点有标号的无根树,直径为0,1,…,n−1的树有多少个。(n<=500)

div2版本:题意同div2,n<=11

C.Division

div1版本:你有一个数列a[1], a[2],...,a[n-1]​,a[n]​。进行q次询问,每次询问对区间 [l,r] 内的数你可以进行这样的一次操作,每次选择数列中其中一个数然后将其除2下取整,每次询问只能对这些数进行不超过k次操作,如何操作才能使得这些数的和尽可能的小。

简易题解:我们可以把问题转化一下,因为每次操作都是将一个数除二向下取整,我们可以看做减去了某一个数,这样对于每个数a[i]来说,这个数最多进行log(a[i])次操作之后,这个数就会变成0了,我们可以把这log(a[i])次减掉的数都算出来。而每次都是要对区间内的数进行k次操作,所以我们就可以看成是将这个区间内每个数操作后会减去的数的前k大数减掉,就是最终的结果了。

这样我们就可以用一个主席树来维护这n×log(a[i])个数,然后区间查询前k大和就可以解决这个问题了。

时间复杂度为O(n*log(a[i])*logn+q*logn),空间复杂度为O(n*log(a[i])*logn)。

但是,,,dls的题目怎么会这么简单,如果按照这个做法,你将会收获到一个MLE(手算一下可以发现256M的内存根本不够开下O(n*log(a[i])*logn)的空间。

所以我们要进行一些骚操作对空间进行优化。(听完dls讲课感觉这个做法真的是太神仙了,dlstxdy!)

前面说了,每次除2我们都可以看做是一次减法,那么我们就可以把查询离线下来。

然后枚举k=30到0,每次枚举看有哪些数在一次操作之后所减小的值在[2^(k-1),2^(k)]这个区间内,然后对这些值再重新建立主席树,再将每次询问的答案进行更新,当然这里还得做一个前缀和优化,不然还是会TLE的。对于每个操作次数k大于你所查询的数减少的次数的情况,我们可以用一个前缀和来记录这些所减少的数值,直接减掉即可,否则再用主席树进行一次查询,这样就可以避免每次都进行一次查询,将复杂度从q×logn×30降到了q×logn。

这样由于每次对于所枚举的区间都是重新建立一次主席树,所以只需要n×logn的空间就可以了。

这样的时间复杂度就是O(n*logn*30 + q * logn),空间复杂度是O(n*logn),就可以通过这个题目了。

(ps:可能是我常数太大了,还得加个fastIO才能过,大常数选手流下了鶸鸡的泪水

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pairpii;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "< 32; c = xchar()) * s++ = c;
		*s = 0;
	}
	inline void wchar(int x) {
		if (wpos == S) fwrite(wbuf, 1, S, stdout), wpos = 0;
		wbuf[wpos++] = x;
	}
	inline void wint(ll x) {
		if (x < 0) wchar('-'), x = -x;
		char s[24];
		int n = 0;
		while (x || !n) s[n++] = '0' + x % 10, x /= 10;
		while (n--) wchar(s[n]);
		wchar('\n');
	}
	inline void wstring(const char *s) {
		while (*s) wchar(*s++);
	}
	~FastIO() {
		if (wpos) fwrite(wbuf, 1, wpos, stdout), wpos = 0;
	}
} io;

int n, m;
int a[MX], num[MX], tot;
int root[MX];
ll pre_sum[MX];
struct tree {
	int ls, rs, s;
	ll sum;
} T[MX << 5];
struct que {
	int l, r, k;
	ll ans;
} q[MX * 5];
inline void push_up(int rt) {
	int ls = T[rt].ls, rs = T[rt].rs;
	T[rt].s = T[ls].s + T[rs].s;
	T[rt].sum = T[ls].sum + T[rs].sum;
}
void update(int l, int r, int &rt, int pre, int x) {
	rt = ++tot; T[rt] = T[pre];
	if (l == r) {
		++T[rt].s;
		T[rt].sum += x - (x >> 1);
		return;
	}
	int mid = (l + r) >> 1;
	if (x <= mid) update(l, mid, T[rt].ls, T[pre].ls, x);
	else update(mid + 1, r, T[rt].rs, T[pre].rs, x);
	push_up(rt);
}
ll query(int l, int r, int rt, int pre, int k) {
	if (l == r) return ll(k * (l - (l >> 1)));
	int ls = T[rt].ls, rs = T[rt].rs;
	int LS = T[pre].ls, RS = T[pre].rs;
	int mid = (l + r) >> 1;
	if (k > T[rs].s - T[RS].s)
		return query(l, mid, ls, LS, k - (T[rs].s - T[RS].s)) + (T[rs].sum - T[RS].sum);
	else
		return query(mid + 1, r, rs, RS, k);
}

int main() {
#ifdef ONLINE_JUDGE
#else
	FIN;
#endif
	n = io.xint(); m = io.xint();
	for (int i = 1; i <= n; ++i) {
		a[i] = io.xint();
		pre_sum[i] = pre_sum[i - 1] + a[i];
	}
	for (int i = 1; i <= m; ++i) {
		q[i].l = io.xint();
		q[i].r = io.xint();
		q[i].k = io.xint();
		q[i].ans = pre_sum[q[i].r] - pre_sum[--q[i].l];
	}
	for (int i = 29; i >= 0; --i) {
		int L = 1 << i, R = 2 << i;
		tot = 0;
		for (int j = 1; j <= n; ++j) {
			if ((a[j] >> i) & 1) {
				update(L, R, root[j], root[j - 1], a[j]);
				pre_sum[j] = pre_sum[j - 1] + a[j] - (a[j] >> 1);
				num[j] = num[j - 1] + 1;
			} else {
				root[j] = root[j - 1];
				pre_sum[j] = pre_sum[j - 1];
				num[j] = num[j - 1];
			}
		}
		for (int j = 1; j <= m; ++j) {
			if (q[j].k && q[j].k >= num[q[j].r] - num[q[j].l]) {
				q[j].k -= num[q[j].r] - num[q[j].l];
				q[j].ans -= pre_sum[q[j].r] - pre_sum[q[j].l];
			} else {
				q[j].ans -= query(L, R, root[q[j].r], root[q[j].l], q[j].k);
				q[j].k = 0;
			}
		}
		for (int j = 1; j <= n; ++j) if ((a[j] >> i) & 1) a[j] >>= 1;
	}
	for (int i = 1; i <= m; i++)
		io.wint(q[i].ans);
	return 0;
}

 

div2版本:仅仅只有一次询问,其他同div1。

简易题解:这又是一个div2比div1快乐一百倍的题目,由于每个数最大进行logn次操作就会变成0了,所以当操作次数k大于n×logn次,那么整个序列就会变成全零,对于k小于等于n×logn次的情况,暴力处理就可以了,用一个优先队列维护最大的数即可,每次取出值最大的数处理就行。

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"

typedef double db;
typedef long long ll;
typedef pairpii;
typedef pairpll;
const db eps = 1e-9;
const db pi = acos(-1.0);
const int MX = 1e5 + 5;
//head

int n,k;
int a[MX];
priority_queueq;

int main(){
#ifdef LOCAL
    FIN;
#endif
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= n;i++){
        scanf("%d",&a[i]);
        q.push(a[i]);
    }
    if(k >= 31 * n) printf("0\n");
    else{
        while(k--){
            int now = q.top();q.pop();
            q.push(now/2);
        }
        ll ans = 0;
        while(!q.empty()){
            ans += q.top();
            q.pop();
        }
        printf("%lld\n",ans);
    }
    return 0;
}

D.Doppelblock

这是一个div1和div2相同的题目,题目是中文,就不写了-,-。

简易题解:n最大只有7,很明显就是一个搜索题目。先按照题目的要求将所有限制条件加上,然后就开始无尽的剪枝之旅了。正式比赛的时候,剪枝剪到意识模糊都没过,后面再看代码的时候才发现当时写了很多没用的东西,写这种鬼畜的东西还是得头脑清醒的时候才能写。

这个题只需要加几个关键的剪枝就可以过了。

对于每一行和每一列,我们都可以维护两个值sum、sum1。sum表示这一行(列)还没有填“X”的时候已经填了的数的和,sum1表示这一行(列)已经填了一个"X"之后,从第一个“X”开始到第二个“X”的数的和。由于每一行填下的数的总和是固定的,如果当前这一位要填的数加上sum要大于每行总和减去这位数所在的行(列)的限制和的话,就不用继续枚举了。同理,对于填了一个“X”的情况,如果当前这一位要填的数加上sum1要大于这位数所在的行(列)的限制和的话,也是可以不用继续往下枚举的。

还有就是如果一行(列)已经填了两个“X”的话,直接判断一下这行的sum1是否等于限制和就可以了。

加入以上剪枝的话,就可以过了。

当然还有更优的写法,就是先枚举所有“X”的填充情况,再进行填数,这样可以快很多。(但我太懒了,就没写)。

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pairpii;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "< sum - c[1]) return;
				if (ctag[1] == 1 && i + csum1[1] > c[1]) return;
				if (rvis[x + 1][i] || cvis[1][i]) continue;

				ans[x + 1][1] = i;
				rsum[x + 1] += i;
				if (ctag[1] == 0) csum[1] += i;
				else if (ctag[1] == 1) csum1[1] += i;
				rvis[x + 1][i] = 1; cvis[1][i] = 1;

				dfs(x + 1, 1);

				ans[x + 1][1] = 0;
				rsum[x + 1] -= i;
				if (ctag[1] == 0) csum[1] -= i;
				else if (ctag[1] == 1) csum1[1] -= i;
				rvis[x + 1][i] = 0; cvis[1][i] = 0;
			}
		}
		return;
	}
	for (int i = 0; i < n - 1; i++) {
		if (ok) return;
		if (i == 0) {
			if (rtag[x] == 2 || ctag[y + 1] == 2) continue;
			ans[x][y + 1] = -1;
			rtag[x]++; ctag[y + 1]++;

			dfs(x, y + 1);

			ans[x][y + 1] = 0;
			rtag[x]--; ctag[y + 1]--;
		} else {
			if (rvis[x][i] || cvis[y + 1][i]) continue;
			if ((y + 1 == n && rtag[x] == 1) || (y + 1 == n - 1 && rtag[x] == 0)) return;

			if (rtag[x] == 0 && i + rsum[x] > sum - r[x]) return;
			if (rtag[x] == 1 && i + rsum1[x] > r[x]) return;

			if (ctag[y + 1] == 0 && i + csum[y + 1] > sum - c[y + 1]) return;
			if (ctag[y + 1] == 1 && i + csum1[y + 1] > c[y + 1]) return;

			ans[x][y + 1] = i;
			rvis[x][i] = cvis[y + 1][i] = 1;
			if (rtag[x] == 0) rsum[x] += i;
			else if (rtag[x] == 1) rsum1[x] += i;

			if (ctag[y + 1] == 0) csum[y + 1] += i;
			else if (ctag[y + 1] == 1) csum1[y + 1] += i;

			dfs(x, y + 1);

			ans[x][y + 1] = 0;
			rvis[x][i] = cvis[y + 1][i] = 0;
			if (rtag[x] == 0) rsum[x] -= i;
			else if (rtag[x] == 1) rsum1[x] -= i;

			if (ctag[y + 1] == 0) csum[y + 1] -= i;
			else if (ctag[y + 1] == 1) csum1[y + 1] -= i;
		}
	}
}

int main() {
	// FIN;
	int t = 0;
	for (scanf("%d", &_); _; _--) {
		if (t) puts("");
		t++;
		init();
		scanf("%d", &n);
		for (int i = 1; i <= n; i++) scanf("%d", &r[i]);
		for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
		sum = (1 + (n - 2)) * (n - 2) / 2;
		ok = 0;
		for (int i = 0; i < n - 1; i++) {
			if (ok) break;
			if (i == 0) {
				ans[1][1] = -1;
				rtag[1]++; ctag[1]++;

				dfs(1, 1);

				ans[1][1] = 0;
				rtag[1]--; ctag[1]--;
			} else {
				ans[1][1] = i;
				rsum[1] += i; csum[1] += i;
				rvis[1][i] = 1; cvis[1][i] = 1;

				dfs(1, 1);

				ans[1][1] = 0;
				rsum[1] -= i; csum[1] -= i;
				rvis[1][i] = 0; cvis[1][i] = 0;
			}
		}
	}
	return 0;
}

E.Fast Kronecker Transform

一个神奇的卷积题目。

这个题我们如果考虑枚举值去直接做NTT的话,在NTT中就是将所有值等于 i 的位上的值附值为那一位,否则就为0。

这样对于每一个值 i 都做一遍NTT是可以得到答案的。

但是如果某一个值所有的数的个数很少的话,直接做NTT是很慢的(因为考虑到每次都要初始化NTT中的数组,而且NTT中需要进行多次的取模运算,所以就要慢的多)。

对于值的个数较小的情况,我们就考虑直接用暴力做,这样就可以节省下较多的时间,对于值较多的情况,再考虑用NTT,就可以降低复杂度了。

假设我们设置的阈值为T,那么复杂度大致就是O(num*T+n/T*(n/T)*log(n/T)),接下来就是进行快乐地调参了~

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pairpii;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<>= 1;
        a = a * a % mod;
    }
    return ans;
}
void GetWn() {
    for (int i = 0; i < NUM; i++) {
        int t = 1 << i;
        wn[i] = quick_mod(G, (P - 1) / t, P);
    }
}
void Rader(ll F[], int len) {
    int j = len >> 1;
    for (int i = 1; i < len - 1; i++) {
        if (i < j) swap(F[i], F[j]);
        int k = len >> 1;
        while (j >= k)j -= k, k >>= 1;
        if (j < k) j += k;
    }
}
void NTT(ll F[], int len, int t) {
    Rader(F, len);
    int id = 0;
    for (int h = 2; h <= len; h <<= 1) {
        id++;
        for (int j = 0; j < len; j += h) {
            ll E = 1;
            for (int k = j; k < j + h / 2; k++) {
                ll u = F[k];
                ll v = E * F[k + h / 2] % P;
                F[k] = (u + v) % P;
                F[k + h / 2] = (u - v + P) % P;
                E = E * wn[id] % P;
            }
        }
    }
    if (t == -1) {
        for (int i = 1; i < len / 2; i++)swap(F[i], F[len - i]);
        ll inv = quick_mod(len, P - 2, P);
        for (int i = 0; i < len; i++)F[i] = F[i] * inv % P;
    }
}
void Conv(ll a[], ll b[], int len) {
    NTT(a, len, 1);
    NTT(b, len, 1);
    for (int i = 0; i < len; i++) a[i] = a[i] * b[i] % P;
    NTT(a, len, -1);
}

int n, m;
int a[MX], b[MX];
vectorh, la[MX], lb[MX];
int get_id(int x) {
    return lower_bound(all(h), x) - h.begin() + 1;
}
ll ans[2 * MX];
void upd(ll &x, ll y) {
    x += y;
    if (x >= P) x -= P;
}

int main() {
#ifdef ONLINE_JUDGE
#else
    FIN;
#endif
    GetWn();
    scanf("%d%d", &n, &m);
    for (int i = 0; i <= n; ++i) {
        scanf("%d", &a[i]);
        h.pb(a[i]);
    }
    for (int i = 0; i <= m; ++i) {
        scanf("%d", &b[i]);
        h.pb(b[i]);
    }
    sort(all(h));
    h.erase(unique(all(h)), h.end());
    for (int i = 0; i <= n; ++i) {
        a[i] = get_id(a[i]);
        la[a[i]].pb(i);
    }
    for (int i = 0; i <= m; ++i) {
        b[i] = get_id(b[i]);
        lb[b[i]].pb(i);
    }
    int len = 1;
    while (len <= n + m + 1) len <<= 1;
    int up = 4000;
    for (int i = 1; i <= (int)h.size(); ++i) {
        if (min(la[i].size() , lb[i].size()) <= up) {
            for (auto x : la[i]) for (auto y : lb[i]) upd(ans[x + y], (ll)x * y % P);
        } else {
            for (int j = 0; j <= n; ++j) va[j] = (a[j] == i) ? j : 0;
            for (int j = n + 1; j < len; ++j) va[j] = 0;
            for (int j = 0; j <= m; ++j) vb[j] = (b[j] == i) ? j : 0;
            for (int j = m + 1; j < len; ++j) vb[j] = 0;
            Conv(va, vb, len);
            for (int j = 0; j <= n + m; ++j) upd(ans[j], va[j]);
        }
    }
    for (int i = 0; i <= n + m; ++i)
        printf("%lld%c", ans[i], " \n"[i == n + m]);
    return 0;
}

F.Kropki

div1版本:你有一个1到n的排列p[1],p[2],p[3],...,p[n]​,对于所有的i (1≤ i ≤n−1),如果p[i] 和 p[i+1]​中,有一个数是另一个的两倍,那么会在这两个数之间画上一个点,否则不会。

现在你把所有数字都擦掉了,只剩下了这些点,请问有多少种1到n的排列满足条件。(n<=40)(待补)

div2版本:n<=15,其他同div1。

简易题解:这又是一个div2比div1快乐不知道多少的题目,(虽然队友昨天下午wa到无法自拔)。

由于n<=15,所以就可以直接考虑做状压dp了,具体咋转移还没细想(毕竟是队友写的,逃

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
#define LL long long
using namespace std;

#define fuck1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define fuck2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define fuck3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"

typedef long long ll;
typedef pairpii;
typedef pairpll;
const int mod = 1e9+7;
const int MX = 1<<16;
int dp[16][MX][16];
int vis[16];
char s[16];
inline void upd(int &x){
    if(x > mod) x -= mod;
}
int main()
{
#ifdef LOCAL
    FIN;
#endif // LOCAL
    int n;
    scanf("%d",&n);
    scanf("%s",s+1);
    for(int i = 1; i < n; i++)
        vis[i] = s[i]-'0';
    for(int i = 0; i < n; i++)
        dp[0][1< 0){
                if(!vis[i]) {
                    for(int l = 0; l < n; l++) if( ((1<

G.Least Common Multiple(待补)

作为一个神仙题,这个题很好的尽到了它的职责。昨天听dls讲题都听得一脸懵逼,感觉大概率是咕咕咕了?(逃

H.Nested Tree

div1版本:你有一棵n个点树T,然后你把它复制了m遍,然后在这m棵树之间又加了m−1条边,变成了一棵新的有nm个点的树T2​。求T2​中所有点对的距离和.(n,m<=1e5)

简易题解:好几天前就想补这题了,由于种(wo)种(xiang)原(mo)因(yu),到今天才把这题补完,人类的本质果然是鸽子~

由于div1版本的n和m都是1e5,所以我们没办法直接建树去考虑每条边的贡献,但仍旧是沿着这个思路去做的,考虑每条边的贡献。

对于大的树T_{2},我们也可以直接做一遍dfs,算出所有连接复制的树之间的边的贡献。

接下来,我们可以考虑每一棵小树中的边对于答案的贡献,我们知道每条边对答案的贡献就是 size*(n*m-size)size所表示的就是这条边连接的一端的点(深度较大的点)所在的子树大小。

那么对于这题,同样也可以这么去求,我们可以把这个看成是若干棵小树悬挂在当前这棵树的下方(我们假设所有小子树的根节点都是1)。

比如样例中,对于复制后的第一份的小树来说,就可以把整棵树看成是如下的样子:

CCPC-Wannafly Winter Camp Day5(div1 + div2 部分题解)_第1张图片

这样对于第一个复制的小树来说,节点1的子树大小就为9,节点2的子树大小为8,节点3的子树大小为4,所以这棵小子树对最终答案的贡献为:8*(9-8)+4*(9-4)=28

同理,第二个复制的小树,节点1的子树大小就为9,节点2的子树大小为2,节点3的子树大小为1,所以这棵小子树对最终答案的贡献为:2*(9-2)+1*(9-1)=22

第二个复制的小树,节点1的子树大小就为9,节点2的子树大小为8,节点3的子树大小为1,所以这棵小子树对最终答案的贡献为:8*(9-8)+1*(9-1)=16

再加上每条连接复制树直接的边的贡献28+22+16+3*6+3*6=102

那现在的问题就转化成了,该如何维护每棵小子树内所有节点的子树大小了。

我们可以看出来,在点u悬挂一棵子树之后,只会对根节点到点u这条链上的点的子树大小有影响,那么我们就可以用树链剖分对这条链上所有的点的子树大小进行更新。

但由于每棵子树中节点个数都很多,如果一个节点一个节点算的话,时间复杂度是过不去的。

现在我们假设在当前所枚举的子树中,节点v的子树大小为size[v],那么这棵子树对答案最终的贡献就为\sum_{v=2}^{n}size[v]*(n*m-size[v])。(因为选择节点1为根节点的话,就没有一条边是以节点1为深度更深的节点了,所以就不考虑节点1).

考虑拆开一下括号,则可得\sum_{v=2}^{n}size[v]*n*m-size[v]^2,由于n*m是一个常数,那么我们只需要维护\sum_{v=2}^{n}size[v]\sum_{v=2}^{n}size[v]^2就可以了。

\sum_{v=2}^{n}size[v]可以借助线段树来维护。

对于\sum_{v=2}^{n}size[v]^2同样也可以用线段树来维护,根据(A+B)^2 = A^2+2*A*B+B^2,我们只需要在线段树更新的时候,把size[v]^2的和按照这个式子更新即可,即(假设子树大小增加了d)(size[v]+d)^2=size[v]^2+2*d*size[v]+d^2

这样就可以通过枚举每棵子树算其对答案的贡献解决这个问题了,时间复杂度为O(n*logn*logn)。

在写的时候注意下取模的细节就可以了。

参考代码:

#include 
#define fi first
#define se second
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pairpii;
typedef pairpll;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<g[MX];
vectorG[MX];
int SZ[MX], dep1[MX];
ll ans, all;

void dfs(int u, int fa) {
    SZ[u] = 1; dep1[u] = dep1[fa] + 1;
    for (auto now : G[u]) {
        int v = now.fi;
        if (v == fa) continue;
        dfs(v, u);
        SZ[u] += SZ[v];
        ll A = (ll)SZ[v] * n % mod;
        ll B = (all - A + mod) % mod;
        ans = (ans % mod + A * B % mod) % mod;
    }
}
int dep[MX], sz[MX], fa[MX], son[MX], top[MX], id[MX], _id[MX], tot;
void dfs1(int u) {
    sz[u] = 1; son[u] = 0;
    for (auto v : g[u]) {
        if (v == fa[u]) continue;
        fa[v] = u; dep[v] = dep[u] + 1;
        dfs1(v);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}
void dfs2(int u, int tp) {
    id[u] = ++tot; _id[tot] = u; top[u] = tp;
    if (son[u]) dfs2(son[u], tp);
    for (auto v : g[u]) {
        if (v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}
void pre_solve() {
    tot = 0;
    dfs1(1); dfs2(1, 1);
}
ll sum[MX << 2], sq_sum[MX << 2], tag[MX << 2];
void push_up(int rt) {
    sum[rt] = (sum[rt << 1] + sum[rt << 1 | 1]) % mod;
    sq_sum[rt] = (sq_sum[rt << 1] + sq_sum[rt << 1 | 1]) % mod;
}
void push_down(int l, int r, int rt) {
    if (tag[rt]) {
        int mid = (l + r) >> 1;
        sq_sum[rt << 1] = (sq_sum[rt << 1] % mod + 2ll * tag[rt] % mod * sum[rt << 1] % mod + tag[rt] * tag[rt] % mod * (mid - l + 1) % mod) % mod;
        sum[rt << 1] = (sum[rt << 1] % mod + tag[rt] * (mid - l + 1) % mod) % mod;
        sq_sum[rt << 1 | 1] = (sq_sum[rt << 1 | 1] + 2ll * tag[rt] % mod * sum[rt << 1 | 1] % mod + tag[rt] * tag[rt] % mod * (r - mid) % mod) % mod;
        sum[rt << 1 | 1] = (sum[rt << 1 | 1] % mod + tag[rt] * (r - mid) % mod) % mod;
        tag[rt << 1] += tag[rt]; tag[rt << 1] %= mod;
        tag[rt << 1 | 1] += tag[rt]; tag[rt << 1 | 1] %= mod;
        tag[rt] = 0;
    }
}
void build(int l, int r, int rt) {
    if (l == r) {
        sum[rt] = sz[_id[l]];
        sq_sum[rt] = (ll)sz[_id[l]] * sz[_id[l]] % mod;
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    push_up(rt);
}
void update(int L, int R, ll d, int l, int r, int rt) {
    if (L <= l && r <= R) {
        sq_sum[rt] = (sq_sum[rt] % mod + 2ll * d % mod * sum[rt] % mod + d * d % mod * (r - l + 1) % mod) % mod;
        sum[rt] = (sum[rt] % mod + d * (r - l + 1) % mod) % mod;
        tag[rt] += d; tag[rt] %= mod;
        return;
    }
    push_down(l, r, rt);
    int mid = (l + r) >> 1;
    if (L <= mid) update(L, R, d, lson);
    if (R > mid) update(L, R, d, rson);
    push_up(rt);
}
ll query_sum(int L, int R, int l, int r, int rt) {
    if (L <= l && r <= R) return sum[rt];
    push_down(l, r, rt);
    ll res = 0;
    int mid = (l + r) >> 1;
    if (L <= mid) res = (res + query_sum(L, R, lson)) % mod;
    if (R > mid) res = (res + query_sum(L, R, rson)) % mod;
    return res;
}
ll query_sq(int L, int R, int l, int r, int rt) {
    if (L <= l && r <= R) return sq_sum[rt];
    push_down(l, r, rt);
    ll res = 0;
    int mid = (l + r) >> 1;
    if (L <= mid) res = (res + query_sq(L, R, lson)) % mod;
    if (R > mid) res = (res + query_sq(L, R, rson)) % mod;
    return res;
}
void upd(int u, int v, ll d) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        update(id[top[u]], id[u], d, 1, n, 1);
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    update(id[u], id[v], d, 1, n, 1);
}
pll cal(int L, int R) {
    ll res1 = query_sum(L, R, 1, n, 1);
    ll res2 = query_sq(L, R, 1, n, 1);
    return MP(res1, res2);
}

int main() {
#ifdef ONLINE_JUDGE
#else
    FIN;
#endif
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].pb(v); g[v].pb(u);
    }
    for (int i = 1; i < m; i++) {
        int a, b, u, v;
        scanf("%d%d%d%d", &a, &b, &u, &v);
        G[a].pb(MP(b, u));
        G[b].pb(MP(a, v));
    }
    all = (ll)n * m % mod;
    dfs(1, 0);
    pre_solve();
    build(1, n, 1);
    for (int i = 1; i <= m; i++) {
        for (auto now : G[i]) {
            ll d = dep1[i] < dep1[now.fi] ? 1ll * SZ[now.fi] * n % mod : ((all % mod - 1ll * SZ[i] * n % mod) % mod + mod) % mod;
            upd(1, now.se, d);
        }

        pll p = cal(2, n);
        ans = (ans % mod + (all * p.fi % mod - p.se % mod) % mod + mod) % mod;
        for (auto now : G[i]) {
            ll d = dep1[i] < dep1[now.fi] ? 1ll * SZ[now.fi] * n % mod : ((all % mod - 1ll * SZ[i] * n % mod) % mod + mod) % mod;
            upd(1, now.se, -d);
        }
    }
    cout << ans << endl;
    return 0;
}

div2版本:n,m<=1000

简易题解:看了这个题,越发感觉div2是真的快乐,嘿嘿。div2版本按照题意建树之后也只有1e6个节点,那就直接建树即可。接着再用一个dfs维护一遍每个子树的大小,就可以去算每条边的贡献了,每条边的贡献就是这条边左侧的节点个数×右侧的节点个数。快乐的维护一下就行了。

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"

typedef double db;
typedef long long ll;
typedef pairpii;
typedef pairpll;
const db eps = 1e-9;
const db pi = acos(-1.0);
const int MX = 1e6 + 5;
const int mod = 1e9 + 7;
//head

int n,m;
vectorG[MX];
int sz[MX];
ll ans = 0;

void dfs(int u,int fa){
    sz[u] = 1;
    for(auto v : G[u]){
        if(v == fa) continue;
        dfs(v,u);
        sz[u] += sz[v];
        ans = (ans % mod + (ll)sz[v] * (n * m - sz[v]) % mod) % mod;
    }
}

int main(){
#ifdef LOCAL
    FIN;
#endif
    scanf("%d%d",&n,&m);
    for(int i = 1;i < n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        for(int j = 0;j < m;j++){
            G[u+j*n].pb(v+j*n);
            G[v+j*n].pb(u+j*n);
        }
    }
    for(int i = 1;i < m;i++){
        int a,b,u,v;
        scanf("%d%d%d%d",&a,&b,&u,&v);
        G[u+(a-1)*n].pb(v+(b-1)*n);
        G[v+(b-1)*n].pb(u+(a-1)*n);
    }
    dfs(1,0);
    cout << ans << endl;
    return 0;
}

I.Sorting

这是一个div1和div2相同的题目。大概题意就是,给一个序列,有以下三种操作:

1、求区间[l,r]的和

2、对区间 [l,r] 进行如下操作:将所有小于等于x的数按原来的顺序放到左边,所有大于x的数按原来的顺序放到右边,再拼成一个新的序列放回区间[l,r]

3、对区间 [l,r] 进行如下操作:将所有小于等于x的数按原来的顺序放到右边,所有大于x的数按原来的顺序放到左边,再拼成一个新的序列放回区间[l,r]

简易题解:对于2、3这两种操作,我们可以发现不会如何进行操作,那些小于等于x的数的在序列内相对顺序是不会变的,同理大于x的数也是一样的。比如1 5 3 2 4 6,x = 3,对区间[2,5]进行操作2,序列就会变成1 3 2 5 4 6,而对于小于等于x的数,在进行操作前的顺序是1 3 2,对于大于x的数原来的顺序是 5 4 6,进行操作后也是一样的。所以我们就可以把区间内小于等于x的数当做是0,大于x的数当做是1。这样问题就转换成了维护区间内0和1的位置关系。

每次操作2就变成了,将[l,l+num-1]内变成0(num为区间内原来0的个数),将[l+num,r]内的数变成1;

操作3就变成了,将[l,l+num-1]内变成1(num为区间内原来1的个数),将[l+num,r]内的数变成0;

查询操作的话,就直接查询区间[l,r]之间的0是从第几个到第几个的,(因为相对顺序是不会变的,所以我们可以直接维护一个前缀和),对于1也同理,这样就可以直接用线段树和前缀和去维护了。

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pairpii;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<> 1;
		tag[rt << 1] = tag[rt << 1 | 1] = tag[rt];
		tree[rt << 1] = (m - l + 1) * tag[rt];
		tree[rt << 1 | 1] = (r - m) * tag[rt];
		tag[rt] = -1;
	}
}
void build(int l, int r, int rt) {
	tag[rt] = -1;
	tree[rt] = 0;
	if (l == r) {
		tree[rt] = (a[l] > x);
		return;
	}
	int m = (l + r) >> 1;
	build(lson);
	build(rson);
	push_up(rt);
}
void update(int L, int R, int d, int l, int r, int rt) {
	if (L <= l && r <= R) {
		tree[rt] = (r - l + 1) * d;
		tag[rt] = d;
		return;
	}
	push_down(l, r, rt);
	int m = (l + r) >> 1;
	if (L <= m) update(L, R, d, lson);
	if (R > m) update(L, R, d, rson);
	push_up(rt);
}
int query(int L, int R, int l, int r, int rt) {
	if (L > R) return 0;
	if (L <= l && r <= R) return tree[rt];
	push_down(l, r, rt);
	int m = (l + r) >> 1, res = 0;
	if (L <= m) res += query(L, R, lson);
	if (R > m) res += query(L, R, rson);
	return res;
}

ll ask(int L, int R) {
	int rb = query(1, R, 1, n, 1);
	int lb = query(1, L - 1, 1, n, 1);
	return (sum1[rb] - sum1[lb]) + (sum0[R - rb] - sum0[L - 1 - lb]);
}

int main() {
	// FIN;
	scanf("%d%d%d", &n, &q, &x);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		if (a[i] <= x) {
			cnt0++;
			sum0[cnt0] = sum0[cnt0 - 1] + a[i];
		} else {
			cnt1++;
			sum1[cnt1] = sum1[cnt1 - 1] + a[i];
		}
	}
	build(1, n, 1);
	int op, l, r;
	while (q--) {
		scanf("%d%d%d", &op, &l, &r);
		if (op == 1) printf("%lld\n", ask(l, r));
		else if (op == 2) {
			int num = query(l, r, 1, n, 1);
			num = (r - l + 1) - num;
			update(l, l + num - 1, 0, 1, n, 1);
			update(l + num, r, 1, 1, n, 1);
		} else {
			int num = query(l, r, 1, n, 1);
			update(l, l + num - 1, 1, 1, n, 1);
			update(l + num, r, 0, 1, n, 1);
		}
	}
	return 0;
}

J.Special Judge

这也是一个div1和div2相同的题目,div1的签到题。突然感觉div2真的好快乐。

这个题就是给你一个图的信息,再给你每个节点在二维平面中的坐标信息,问你按原来的图中的边在二维平面中连边会有多少对边会相交(如果端点相同切交点只有端点就不算)

简易题解:这就是一个简单的几何题。(虽然被题意杀了,队友看错了好久题目,后面还是靠着dls发的通知才悟到了真正的题意)。这就直接判线段相交就可以了,当然有一种特殊情况,就是两条线段虽然是同端点,但是他们是两条线段重合了的,用点积和叉积特判一下就可以了,叉积等于0,点积大于0就是这种情况,其他就是板子了。当然这个题坐标比较大,用double的话可能会有浮点误差,改成用long long就可以了。

参考代码:

#include 
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define rep(a,b) for(int i = a;i <= b;++i)
#define per(a,b) for(int i = b;i >= a;--i)
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;

#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"

typedef long long ll;
typedef pairpii;
typedef pairpll;
//const ll eps = 1e-9;
//const ll pi = acos(-1.0);
const int MX = 2e5+7;

struct Point {
    ll x, y;
    Point() {}
    Point(ll x,ll y):x(x),y(y) {}
};
typedef Point Vector;
int dcmp(ll x) { //返回x的正负
    if(x == 0)return 0;
    return x<0?-1:1;
}
Vector operator-(Vector A,Vector B) {return Vector(A.x - B.x, A.y - B.y);}
Vector operator+(Vector A,Vector B) {return Vector(A.x + B.x, A.y + B.y);}
Vector operator*(Vector A,ll p) {return Vector(A.x*p, A.y*p);}
Vector operator/(Vector A,ll p) {return Vector(A.x/p, A.y/p);}
bool operator<(const Point&a,const Point&b) {return a.x 0) ans++;
                continue;
            }
            if(SegmentProperIntersection(p[vis[i].fi],p[vis[i].se], p[vis[j].fi], p[vis[j].se])){
                ans++;
            }
        }
    }
    cout<

 

你可能感兴趣的:(数论,dp,数据结构,ACM,计算几何,贪心,wannafly,camp)