20210720 noip21

又是原题,写下题解吧

Median

首先时限有 2s(学校评测机太烂,加到 4s 了),可以放心地筛 \(1e7\) 个质数并算出 \(s_2\),然后问题变为类似滑动求中位数。发现 \(s_2\) 几乎是随机的,可以用桶来维护中位数指针,每次暴力移动。

实现比较精妙

code
const int N = 1e7+5;
int n,k,w;

int pri,mid,p[N],s[N],s2[N],cnt[N*2];
bool vis[179424674];

void sieve(int n) {
	For(i,2,n) {
		if( !vis[i] ) p[++pri] = i;
		for(int j = 1; j <= pri && i*p[j] <= n; ++j) {
			vis[i*p[j]] = 1;
			if( i % p[j] == 0 ) break;
		}
	}
}

namespace sub1 {
int mid,pre,cur=-1;
LL ans;
void main() {
	mid = k/2+1;
	for(int i = 1; i < k; ++i) ++cnt[s2[i]];
	For(i,k,n) {
		++cnt[s2[i]], pre += s2[i]<=cur;
		if( i > k ) --cnt[s2[i-k]], pre -= s2[i-k]<=cur;
		while( pre < mid ) pre += cnt[++cur];
		while( pre-cnt[cur] >= mid ) pre -= cnt[cur--];
		ans += cur;
	}
	printf("%lld.0",ans);
	exit(0);
}
}
namespace sub2 {
int mid,prel,prer,l=-1,r=-1;
LL ans;
void main() {
	mid = k/2;
	for(int i = 1; i < k; ++i) ++cnt[s2[i]];
	For(i,k,n) {
		++cnt[s2[i]], prel += s2[i]<=l, prer += s2[i]<=r;
		if( i > k ) --cnt[s2[i-k]], prel -= s2[i-k]<=l, prer-= s2[i-k]<=r;
		while( prel < mid ) prel += cnt[++l];
		while( prel-cnt[l] >= mid ) prel -= cnt[l--];
		while( prer < mid+1 ) prer += cnt[++r];
		while( prer-cnt[r] >= mid+1 ) prer -= cnt[r--];
		ans += l+r;
	}
	printf("%.1lf",ans/2.0);
	exit(0);
}
}

signed main() {
	sieve(179424673);
	read(n,k,w); mid = k/2+1;
	For(i,1,n) s[i] = (LL)p[i] * i %w, s2[i] = s[i] + s[i/10+1];
	if( k & 1 ) sub1::main();
	else sub2::main();
}

Game

澄清题意:“分数差”指 Alice 的得分减 Bob 的得分

考虑用桶来维护 \(s\) 集合。设 \(mx\) 为桶中最大元素,则如果新加进来的数大于 \(mx\),那么一定会立刻被选择,因此它就不用加入桶;否则移动 \(mx\) 找到桶中最大元素。整个过程中 \(mx\) 是单减的。时间复杂度 \(O(nk)\)

需要大力卡常

code
const int N = 1e5+5;
int n,T,a[N],p;

int mx,lsh[N],cnt[N];

void solve() {
	LL ans = 0;
	read(p);
	for(int i = 1; i < p; ++i) {
		++cnt[a[i]];
		if( a[i] > mx ) mx = a[i];
	}
	int f = 1;
	for(int i = p; i <= n; ++i, f = -f) {
		if( a[i] >= mx ) { ans += f * lsh[a[i]]; continue; }
		++cnt[a[i]];
		while( !cnt[mx] ) --mx;
		ans += f * lsh[mx], --cnt[mx];
	}
	for(int i = 1; i < p; ++i, f = -f) {
		while( !cnt[mx] ) --mx;
		ans += f * lsh[mx], --cnt[mx];
	}
	write(ans);
}

signed main() {
	read(n,T);
	For(i,1,n) read(a[i]), lsh[i] = a[i];
	sort(lsh+1,lsh+n+1), lsh[0] = unique(lsh+1,lsh+n+1)-lsh-1;
	For(i,1,n) a[i] = lower_bound(lsh+1,lsh+lsh[0]+1,a[i])-lsh;
	while( T-- ) solve();
	return iocl();
}

Park

原题(不知道出题人改的什么鬼题面)

考虑先固定一个点,那么就可以直接 DP。时间复杂度 \(O(n^2)\)

树上路径类问题通常尝试在 LCA 处将两条链拼起来,本题也不例外。发现对于一条路径,起点和终点是不等价的,因此设 \(f[i,j]\) 表示从 \(i\) 的子树走到 \(i\)\(j\) 个面包的最大差值,\(g[i,j]\) 为从 \(i\) 的子树走到 \(i\) 的。
转移见代码

code
const int N = 1e5+5;
int n,m,val[N];
vector<int> to[N];

LL ans,sum[N],f[N][105],g[N][105];

void dfs(int u,int fa) {
	For(i,1,m) f[u][i] = sum[u], g[u][i] = sum[u]-val[fa];
	//g[u]会在fa处用到(从fa走下来),无法让小朋友在u遇到fa的鸽子而自己不遇到
	for(int v : to[u]) if( v != fa ) {
		dfs(v,u);
		For(i,0,m) ans = max(ans,f[u][i]+g[v][m-i]);
		// v之前的子树 -> u -> v -> 子树v
		For(i,1,m)
			f[u][i] = max(f[u][i],max(f[v][i],f[v][i-1]+sum[u]-val[v])),
			g[u][i] = max(g[u][i],max(g[v][i],g[v][i-1]+sum[u]-val[fa]));
			// 不变;u不放;u放
	}
}

signed main() {
	read(n,m);
	For(i,1,n) read(val[i]);
	for(int i = 1; i < n; ++i) {
		int x,y; read(x,y);
		to[x].pb(y), to[y].pb(x);
		sum[x] += val[y], sum[y] += val[x];
	}
	dfs(1,0);
	For(i,1,n) reverse(to[i].begin(),to[i].end());
	// 答案与顺序有关,需要反转儿子来统计全
	dfs(1,0);
	write(ans);
	return iocl();
}
posted @ 2021-07-21 09:28  401rk8  阅读(36)  评论(0编辑  收藏  举报