2019 Multi-University Training Contest 9 HDU多校赛 题解

HDU 6680 01.Rikka with Quicksort

题意

给个递推式
gm(i)=00imgm(i)=i1+1ij=1i(gm(j)+gm(ij1))i>megin{aligned} g_m(i)&=0 & 0le ile m&\ g_m(i)&=i-1+frac 1isum_{j=1}^{i}(g_m(j)+g_m(i-j-1)) & i>m& end{aligned}
gm(n)g_m(n)在模109+710^9+7意义下的值。
1mn1091le mle nle 10^9

题解

gm(i)=ai,Sn=i=1naig_m(i)=a_i,S_n=sum_{i=1}^na_i
然后对于n>mn>m有:
an=n1+2nSn1Sn=n1+n+2nSn1Sn(n+1)(n+2)=n1(n+1)(n+2)+Sn1n(n+1)egin{aligned}a_n&=n-1+frac2nS_{n-1}\S_n&=n-1+frac{n+2}nS_{n-1}\frac{S_n}{(n+1)(n+2)}&=frac{n-1}{(n+1)(n+2)}+frac{S_{n-1}}{n(n+1)}end{aligned}f(n)=Sn(n+1)(n+2)f(n)=frac{S_n}{(n+1)(n+2)},则
f(n)=f(n1)+n1(n+1)(n+2)f(n)=f(n-1)+frac{n-1}{(n+1)(n+2)}
那么我们分段打表出上面那个分数的前缀和,然后只用计算剩下的一部分。求逆元用O(n)O(n)的写法,先乘起来再求逆元。

打表的方法就是每2e5个数一起计算。

这样2e5分一块,一共5000块。然后就苟到rk1了(目前)。

CODE

代码太长卡文章编辑器


HDU 6681 02.Rikka with Cake

题意

一个矩形被kk条射线分割,问形成了多少个区域。
保证射线端点没有x坐标重复且没有y坐标重复。
k<=1e5

题解

利用欧拉公式,VE+F=2V-E+F=2,则F=EV+2F=E-V+2

设与第ii条射线相交的射线有cic_i条,ci=Csum c_i=CCC也就是射线的交点数。

考虑点数VV

  • 矩形的四个角以及射线的端点贡献了k+4k+4
  • 射线与矩形四条边贡献了kk
  • 射线与射线贡献了CC

V=C+2k+4V=C+2k+4

考虑边数EE

  • 一条射线ii被其他射线截成了ci+1c_i+1段,所有的加起来就贡献了2C+k2C+k。因为每个交点需要被算两次。
  • 线段在矩形四周上截了kk次,所以一共贡献了k+4k+4

E=2C+2k+4E=2C+2k+4

F=EV+2=C+2 herefore F=E-V+2=C+2

除去矩形外的部分。
F=C+1 herefore F=C+1

然后离散化后树状数组求交点就行了。O(klog)O(klog)

CODE

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 100010;
int n, m, k, N, M;
int vx[MAXN], vy[MAXN];
LL ans;
struct node {
	int x, y, d;
}p[MAXN];
//0: L
//1: D
//2: R
//3: U
struct Q {
	int x, y;
	Q(){} Q(int x, int y):x(x), y(y){}
	inline bool operator <(const Q &o)const {
		return x < o.x;
	}
}q1[MAXN<<1], q2[MAXN<<1];
struct QQ {
	int x, l, r;
	QQ(){} QQ(int x, int l, int r):x(x), l(l), r(r){}
	inline bool operator <(const QQ &o)const {
		return x < o.x;
	}
}qq[MAXN<<1];
int T[MAXN];
inline void upd(int x, int v) {
	while(x <= M) T[x] += v, x += x&-x;
}
inline int qsum(int x) {
	int re = 0;
	while(x) re += T[x], x -= x&-x;
	return re;
}
inline void solve() {
	int tot1 = 0, tot2 = 0, cur = 0;
	for(int i = 1; i <= k; ++i)
		if(!((p[i].d)&1)) {
			if(p[i].d == 0) q1[++tot1] = Q(p[i].x, p[i].y);
			else q2[++tot2] = Q(p[i].x, p[i].y);
		}
		else {
			int l, r;
			if(p[i].d == 1) l = 1, r = p[i].y;
			else l = p[i].y, r = M-1;
			qq[++cur] = QQ(p[i].x, l, r);
		}
	sort(q1 + 1, q1 + tot1 + 1);
	sort(q2 + 1, q2 + tot2 + 1);
	sort(qq + 1, qq + cur + 1);
	for(int i = 1; i <= M; ++i) T[i] = 0;
	int now = 0;
	for(int i = 1; i <= tot1; ++i) {
		while(now < cur && qq[now+1].x <= q1[i].x) {
			++now;
			upd(qq[now].l, 1);
			upd(qq[now].r+1, -1);
		}
		ans += qsum(q1[i].y);
	}
	for(int i = 1; i <= M; ++i) T[i] = 0;
	now = cur+1;
	for(int i = tot2; i >= 1; --i) {
		while(now > 1 && qq[now-1].x >= q2[i].x) {
			--now;
			upd(qq[now].l, 1);
			upd(qq[now].r+1, -1);
		}
		ans += qsum(q2[i].y);
	}
}
int Main() { //clear!!
	scanf("%d%d%d", &n, &m, &k);
	vx[N=1] = n;
	vy[M=1] = m;
	char s[2];
	for(int i = 1; i <= k; ++i) {
		scanf("%d%d%s", &p[i].x, &p[i].y, s);
		vx[++N] = p[i].x;
		vy[++M] = p[i].y;
		p[i].d = s[0] == 'L' ? 0 : s[0] == 'D' ? 1 : s[0] == 'R' ? 2 : 3;
	}
	sort(vx + 1, vx + N + 1); N = unique(vx + 1, vx + N + 1) - vx - 1;
	sort(vy + 1, vy + M + 1); M = unique(vy + 1, vy + M + 1) - vy - 1;
	for(int i = 1; i <= k; ++i) {
		p[i].x = lower_bound(vx + 1, vx + N + 1, p[i].x) - vx;
		p[i].y = lower_bound(vy + 1, vy + M + 1, p[i].y) - vy;
	}
	ans = 1;
	solve();
	printf("%lld
", ans);
	return 0;
}

int main () { int T; scanf("%d", &T); while(T-->0) Main(); }

HDU6682 03.Rikka with Mista

题意

给出n个数,有2^n种方案选择其中的数,求在所有方案中,选出的数的和在10进制下的4的个数的和。 n<=40

题解

最大的和不超过2e9,所以只有9位可能出现4。按位考虑。

对于第ii位出现的4,一定是sum[410i,510i)(mod10i+1)sumin[4*10^i,5*10^i)pmod{10^{i+1}}。那么我们把数组分成两部分,长度最多都为20。O(n2)O(frac n2)暴力枚举子集求出左右两边所有子集的和。然后在模10i+110^{i+1}排序后就可以two-pointers计数了。分进位和不进位。排序有loglog过不了,所以从小到大枚举位然后基数排序就行了。

CODE

#include<bits/stdc++.h>
using namespace std;
#define vn vector<node>
typedef long long LL;
const int MAXN = 45;
const int MAXS = (1<<20)+5;
struct node { LL x, y; node(){} node(LL x, LL y):x(x), y(y){}};
vn a, b, c[10], d[10];

inline void solve(int *v, int n, vn &re) {
	re.clear();
	for(int i = 0; i < 1<<n; ++i) {
		LL sum = 0;
		for(int j = 0; j < n; ++j)
			if(i&(1<<j)) sum += v[j];
		re.push_back(node(sum, 0));
	}
}

inline LL getans0(vn &l, vn &r, LL bs) {
	int p = r.size()-1; LL re = 0;
	for(node v : l) {
		while(p >= 0 && v.y  + r[p].y >= bs) --p;
		re += p+1;
	}
	return re;
}

inline LL getans1(vn &l, vn &r, LL bs) {
	int p = r.size()-1; LL re = 0;
	for(node v : l) {
		while(p >= 0 && v.y  + r[p].y >= bs) --p;
		re += r.size()-(p+1);
	}
	return re;
}

int n, v[50]; LL ans;
int Main () {
	scanf("%d", &n); ans = 0;
	for(int i = 1; i <= n; ++i) scanf("%d", &v[i]);
	solve(v+1, n/2, a), solve(v+n/2+1, n-n/2, b);
	LL base = 1;
	for(int k = 0, now; k <= 8; ++k, base*=10) {
		for(int i = 0; i < 10; ++i) c[i].clear(), d[i].clear();
		for(node v : a) c[v.x%10].push_back(node(v.x/10, v.y));
		for(node v : b) d[v.x%10].push_back(node(v.x/10, v.y));
		for(int i = 0, k; i < 10; ++i) {
			k = (14-i)%10;
			ans += getans0(c[i], d[k], base);
			k = (13-i)%10;
			ans += getans1(c[i], d[k], base);
		}
		now = 0;
		for(int i = 0; i < 10; ++i) for(node v : c[i]) a[now++] = node(v.x, v.y + i*base);
		now = 0;
		for(int i = 0; i < 10; ++i) for(node v : d[i]) b[now++] = node(v.x, v.y + i*base);
	}
	printf("%lld
", ans);
	return 0;
}

int main() { int T; scanf("%d", &T); while(T--)Main(); }


HDU 6683 04.Rikka with Geometric Sequence

题意

在1~n的序列中求有多少个等比子序列。 n <= 5e17

题解

放上官方题解。

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 5e7 + 5;
const int mod = 998244353;
int phi[MAXN], p[MAXN/10];
bool vis[MAXN];
inline LL mysqrt(LL x) { return (LL)sqrt(x); }
inline int calc(LL n) { n %= mod; return n * (n+1) / 2 % mod; }
map<LL, int>mp;
int S(LL n) {
	if(n <= MAXN-5) return phi[n];
	if(mp.count(n)) return mp[n];
	int re = calc(n);
	for(LL i = 2, j; i <= n; i = j+1) {
		j = n / (n / i);
		(re -= 1ll * (j-i+1) % mod * S(n/i) % mod) %= mod;
	}
	return mp[n] = re;
}
int Main () {
	LL n; scanf("%lld", &n);
	int ans = 0;
	
	//1+2
	(ans += calc(n)) %= mod;
	
	//3
	for(LL i = 2, j; i*i <= n; i = j+1) {
		j = mysqrt(n / (n / i / i));
		(ans += 1ll * (n / i / i) % mod * ((S(j) - S(i-1)) % mod) % mod) %= mod;
	}
	
	//>3
	for(LL a = 2; ; ++a) {
		LL now = a * a;
		if(now > n / a) break;
		now *= a;
		while(now <= n) {
			(ans += 1ll * (n/now) % mod * ((phi[a]-phi[a-1])%mod) % mod) %= mod;
			if(now > n / a) break; now *= a;
		}
	}
	
	printf("%d
", (ans + mod) % mod);
	return 0;
}
inline void pre(int n) {
	int cnt = 0; phi[1] = 1;
	for(int i = 2; i <= n; ++i) {
		if(!vis[i]) p[++cnt] = i, phi[i] = i-1;
		for(int j = 1; i*p[j] <= n; ++j) {
			vis[i*p[j]] = 1;
			if(i % p[j] == 0) { phi[i*p[j]] = phi[i] * p[j]; break; }
			phi[i*p[j]] = phi[i] * (p[j]-1);
		}
	}
	for(int i = 1; i <= n; ++i) (phi[i] += phi[i-1]) %= mod;
}

int main() { pre(MAXN-5); int T; scanf("%d", &T); while(T--)Main(); }


HDU 6684 05.Rikka with Game

题意

两个人对一个只有小写字母的字符串玩游戏,每次可以选择终止游戏或者把一个位置上的字母右移(a->b,b->c,…,z->a)。先手想最小化字典序,后手想最大化字典序。问最终的字符串是什么样的。

题解

显然先手和后手都是不可能右移y的。
先手如果右移,字典序变大,然后后手结束游戏的话先手就亏了。
后手如果右移变成z,先手直接把它变成a,后手就亏大了,相当于一个轮回直接把y变成a。

显然当先手发现局势不可能变得更小的时候会第一回合直接结束游戏。否则就是把出现的的一个z变成a。但是后手可能会把这个z前面的位置变得更大。所以只有当前面全是y,后手不敢轻举妄动的时候先手才能把这个z变成a,然后后手把a变成b,然后先手结束游戏。

代码如下

CODE

#include <bits/stdc++.h>
using namespace std;
int n;
char s[105];
int main () { int Tt;
    scanf("%d", &Tt);
    while(Tt-->0) {
        scanf("%s", s);
		int i = 0, n = strlen(s);
		while(i < n && s[i] == 'y')++i;
		if(s[i] == 'z') s[i] = 'b';
		puts(s);
    }
}

HDU 6685 06.Rikka with Coin

题意

面值有10,20,50,100的硬币,要凑出给出的所有面值,求最小硬币数。

题解

直接枚举10,20,50的个数,然后计算100的至少需要多少个。

注意20,50的可能可以组成一个>100的值。

而且不能直接从大往小放,能放就放,不能就无解。比如有3个20,1个50。60可以用3个20组成,但如果先放了一个50就没法放了。

可以小范围的就写背包,但是我懒得写,就分类讨论一下是否选50面值的。做两次。

代码比较暴力比较长,可读性不高。。。

CODE

#include <bits/stdc++.h>
using namespace std;
int n, w[105];
int main () { int Tt;
    scanf("%d", &Tt);
    while(Tt-->0) {
        scanf("%d", &n); bool flg = 0;
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &w[i]);
            if(w[i] % 10) flg = 1;
            w[i] /= 10; //先把面值除以10
        }
        if(flg) { puts("-1"); continue; }
        int ans = 1<<30;
        for(int a = 0; a <= 1; ++a)
            for(int b = 0; b <= 4; ++b)
                for(int c = 0; c <= 1; ++c) { 
                    int d = 0, A, B, C; bool fff = 1;
                    for(int i = 1; i <= n; ++i) {
                        A = a, B = b, C = c;
                        int t = w[i] / 10, y = w[i] % 10, tmp = 1<<30;
                        if(t) {
                            int T = t-1, Y = y+10;
                            while(Y >= 5 && C) --C, Y -= 5;
                            while(Y >= 2 && B) --B, Y -= 2;
                            while(Y && A) --A, --Y;
                            if(!Y) {
                                T = max(0, T - (A + B*2 + C*5)/10);
                                tmp = min(tmp, T);
                            }
                            T = t-1, Y = y+10;
                            A = a, B = b, C = c;
                            while(Y >= 2 && B) --B, Y -= 2;
                            while(Y && A) --A, --Y;
                            if(!Y) {
                                T = max(0, T - (A + B*2 + C*5)/10);
                                tmp = min(tmp, T);
                            }
                        }
                        A = a, B = b, C = c;
                        while(y >= 5 && C) --C, y -= 5;
                        while(y >= 2 && B) --B, y -= 2;
                        while(y && A) --A, --y;
                        if(!y) {
                            t = max(0, t - (A + B*2 + C*5)/10);
                            tmp = min(tmp, t);
                        }
                        A = a, B = b, C = c;
                        t = w[i] / 10, y = w[i] % 10;
                        while(y >= 2 && B) --B, y -= 2;
                        while(y && A) --A, --y;
                        if(!y) {
                            t = max(0, t - (A + B*2 + C*5)/10);
                            tmp = min(tmp, t);
                        }
                        if(tmp == (1ll<<30)) {fff=0;break;}
                        d = max(d, tmp);
                    }
                    if(fff) ans = min(ans, a + b + c + d);
                }
        printf("%d
", ans);
    }
}

HDU 6686 07.Rikka with Travels

题意

在树上找两条不相交路径,长度分别为(x,y),问形如(x,y)的有序数对有多少。
n<=1e5

题解

显然发现如果我们找到两条路径长度为(x,y)(x,y),那么对于任意i[1,x]j[1,y],(i,j)iin[1,x],jin[1,y],(i,j)都可行,在平面上就是一个矩形。那么我们只要找出所有的极大的(x,y)(x,y),在平面上求矩形的并就行了。

两条不想交路径处理方法就是断一条边,然后两个联通块分别求直径。

  • 当断的边在直径上,两个直径一定各是两边直径的一个端点。就可以在直径上预处理一个前缀最大值和后缀最大值算一下。比如在直径S&gt;TS-&gt;T上,枚举边(u,v)(u,v),定义mx(i)mx(i)表示对于直径上的点ii,它子树中最大的深度,ii的深度定义为11
    答案就是(maxS&gt;i&gt;udis(S,i)+mx(i),maxv&gt;j&gt;Tdis(j,T)+mx(j))(max_{S-&gt;i-&gt;u}dis(S,i)+mx(i),max_{v-&gt;j-&gt;T}dis(j,T)+mx(j))

  • 当断的边不在直径上,直径一定是一条路径,然后在剩下的子树中求直径就行了。注意是要求直径而不是最大深度最大的点。

求矩阵的并直接O(n)O(n)单调栈。但是要排序。所以还是O(nlogn)O(nlogn)

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 100005;
int n, cnt, fir[MAXN], to[MAXN<<1], nxt[MAXN<<1];
inline void link(int u, int v) {
	to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt;
	to[++cnt] = u; nxt[cnt] = fir[v]; fir[v] = cnt;
}
int dis[MAXN];
bool flg[MAXN];
int dfs(int u, int ff) {
	dis[u] = dis[ff] + 1;
	int re = u;
	for(int i = fir[u], v; i; i = nxt[i])
		if((v=to[i]) != ff && !flg[v]) {
			int tmp = dfs(v, u);
			if(dis[tmp] > dis[re]) re = tmp;
		}
	return re;
}
int seq[MAXN], cur, Mxl, pr[MAXN], mx[MAXN];
bool dfs2(int u, int ff) {
	seq[++cur] = u;
	if((dis[u] = dis[ff] + 1) == Mxl) return 1;
	for(int i = fir[u], v; i; i = nxt[i])
		if((v=to[i]) != ff && dfs2(v, u)) return 1;
	--cur;
	return 0;
}
int dfs3(int u, int dep, int ff) {
	int mxd = dep;
	for(int i = fir[u], v; i; i = nxt[i])
		if(!flg[v=to[i]] && v != ff)
			mxd = max(mxd, dfs3(v, dep+1, u));
	return mxd;
}
int tot;
struct node {
	int x, y;
	node(){}
	node(int x, int y):x(x), y(y){}
	inline bool operator <(const node &o)const {
		return x == o.x ? y < o.y : x < o.x;
	}
}p[MAXN<<1];
inline void ins(int x, int y) {
	p[++tot] = node(x, y),
	p[++tot] = node(y, x); //可以互换
}
int q[MAXN];
inline void getans() { //单调栈求矩阵并
	sort(p + 1, p + tot + 1);
	int r = 0;
	for(int i = 1; i <= tot; ++i) {
		while(r && p[q[r]].y <= p[i].y) --r;
		q[++r] = i;
	}
	int cur = 0; LL ans = 0;
	for(int i = 1; i <= n; ++i) {
		while(cur <= r && p[q[cur]].x < i) ++cur;
		if(cur <= r) ans += p[q[cur]].y;
	}
	printf("%lld
", ans);
}
inline int solve(int x) {
	int S = dfs(x, 0), T = dfs(S, 0);
	return dis[T]; 
}
int Main () {
	scanf("%d", &n);
	cnt = cur = tot = 0;
	for(int i = 1; i <= n; ++i) fir[i] = flg[i] = 0;

	for(int i = 1, x, y; i < n; ++i) scanf("%d%d", &x, &y), link(x, y);
	int T = dfs(1, 0), S = dfs(T, 0); Mxl = dis[S];
	dfs2(S, 0);
	
	for(int i = 1; i <= cur; ++i) flg[seq[i]] = 1;
	int Mx = 0;
	for(int i = 1; i <= cur; ++i) {
		mx[i] = dfs3(seq[i], 1, 0), pr[i] = max(pr[i-1], mx[i]+i-1); //预处理前缀最大值
		for(int j = fir[seq[i]], v; j; j = nxt[j])
			if(!flg[v=to[j]]) Mx = max(Mx, solve(v)); //子树求直径
	}
	ins(Mxl, Mx); //断边不在直径上
	int sf = 0;
	for(int i = cur; i >= 2;  --i){
		sf = max(sf, mx[i]+cur-i); //后缀最大值
		ins(pr[i-1], sf); //断边在直径上
	}
	getans();
	return 0;
}


int main() { int T; scanf("%d", &T); while(T--)Main(); }


HDU 6687 08.Rikka with Stable Marriage

题意

n个男生n个女生配对,每个人各有一个数字,两个人配对得到的幸福值是两个数字异或起来。幸福值的和的最大值。n<=1e5

题解

建trie树,要匹配就尽量往不同的方向走。直接暴力匹配。有n对,总时间复杂度为O(nloga)O(nloga)

CODE

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 100005;
const int MAXM = 3000005;
const int DEPP = 29;
struct node {
	int sz1, sz2, val, l, r;
	node(){l=r=sz1=sz2=val=0;}
	inline void Clear() {l=r=sz1=sz2=val=0;}
}t[MAXM];
int n, rt, tot;
LL ans;
inline void upd(int x) {
	t[x].sz1 = t[t[x].l].sz1 + t[t[x].r].sz1;
	t[x].sz2 = t[t[x].l].sz2 + t[t[x].r].sz2;
}
void ins(int &x, int dep, int v, int flg) {
	if(!x) t[x=++tot].Clear();
	if(dep == -1) {
		if(flg == 1) ++t[x].sz1;
		else ++t[x].sz2;
		t[x].val = v;
		return;
	}
	if(v&(1<<dep)) ins(t[x].r, dep-1, v, flg);
	else ins(t[x].l, dep-1, v, flg);
	upd(x);
}
inline void marriage(node &b, node &g) {
	int tmp = min(b.sz1, g.sz2);
	b.sz1 -= tmp, g.sz2 -= tmp;
	ans += 1ll * tmp * (b.val ^ g.val);
}
void match(int b, int g, int dep) {
	if(t[b].sz1 == 0 || t[g].sz2 == 0) return;
	if(dep == -1) {
		marriage(t[b], t[g]); return;
	}
	match(t[b].l, t[g].r, dep - 1); 
	match(t[b].r, t[g].l, dep - 1);
	match(t[b].l, t[g].l, dep - 1);
	match(t[b].r, t[g].r, dep - 1);
	upd(b); upd(g);
}
int Main () {
	tot = ans = 0;
	t[rt = ++tot].Clear();
	scanf("%d", &n);
	for(int i = 1,x; i <= n; ++i) scanf("%d", &x), ins(rt, DEPP, x, 1);
	for(int i = 1,x; i <= n; ++i) scanf("%d", &x), ins(rt, DEPP, x, 2);
	match(1,1,DEPP);
	printf("%lld
", ans);
	return 0;
}


int main() { int T; scanf("%d", &T); while(T--)Main(); }

原文地址:https://www.cnblogs.com/Orz-IE/p/12039240.html