9.12日考试总结

今天花4个小时做了一套6道题,效果不是很理想。


前两题不说了,很简单。



第三题就是大坑了,给出一个字符串,从金字塔顶端向下一层一层地蛇形填充,字符串用完则重头再来,询问某一层一个字母出现了多少次。范围:字符串长度10^6,层数10^18(明明层数几万就可以把所有的非正确算法卡死完了,非要弄那么大)。

这个还是比较好想,首先确定蛇形是个幌子,不影响结果。对于一个询问,求出前面有多少个字母,然后就可以锁定这一行开始的字母是什么了,然后做一个除法,最后特判一下剩余的就行了。但是要注意,等比数列前N项和模字符串长度时,n*(n+1)/2在乘的时候会爆,需要将那个除以二放在n和n+1中是偶数的那里去除,然后两个边分别模了再乘,不然会溢出的。




第四题杀人游戏,N个人,每个人指认一个人,杀手不会指认同党,平民随意指认,计算最多有多少人是杀手,N在五十万以内。首先画成图,一个点若是杀手,则直接连边的点不能是杀手(差不多是个最大独立集)。

我考场上做的时候搞忘这种图的性质了,所以没弄出正解。每个点只有一个出边,说明每个连通块内最多只有一个环,然后枚举割掉环上任意相邻两边的两种情况,较大值就是这个连通块的解了。P.S. yly大蛤考完后把ZJOI2008骑士那道题的代码改了一下,把所有点权全部设为1,直接过了这道题orz。。




第五题:M栋宿舍楼,一共N天,每天有个人到入住到一个指定的宿舍楼,同时会举办一个派对,嘈杂度为该宿舍楼里的人数(包括他自己)。现一共有K次机会可以把任意一栋教学楼里的人轰走,问怎样安排使得嘈杂度之和最小。M<=100,N<=10^6,K<=500.

这种题一般二分或DP。二分不太好检验,就从DP入手吧。先明确顺序不重要,统计出每栋楼一共先后来了多少人就行了。

首先 f[i, j] 表示i个人,分成j部分产生的最小代价。然后再利用求得的f值做一次0~K背包即可。这个算法由两个DP构成,后半部分不会超时,前半部分就会TLE&MLE了。可以想到贪心的思路取而代之,尽量平均分,但是我考场上平均分这里细节没处理好,我想的是前面几部分每部分一样多,最后的余数自成一组,这个是不科学的。事实证明最好是可以分成若干组,使得每组之间的差值不超过1,而且这一定是可以做到的,至于有多少个是下取整,多少个是上取整,用古人解鸡兔同笼那种抬起一只脚的方法就行了。

谢天谢地后面的0~k背包数据范围设置不大。不然再加上队列优化就彻底麻烦啦。

#include<iostream>
#include<cstdio>
#include<cstring>
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
#define LL long long
const int MAXN = 1000010;
const LL INF = 1LL<<60;
int N, M, K;
LL f[505];
int cnt[505];

LL arrange(int n0, int t0) { //n0个分为t0组
	if (n0 % t0 == 0) {
		return ((LL)t0) * (1 + n0/t0) * (n0/t0) / 2LL;
	}
	LL each = n0/t0, more = n0%t0;
	return more*(2+each)*(1+each)/2 + (t0-more)*(1+each)*each/2;
}

void BackPack() {
	for (int i = 1; i<=M; ++i)
		for (int j = K; j>=0; --j) {
			LL mn = INF;
			for (int k = 0; k<=cnt[i] && k<=j; ++k)
				mn = Min(mn, f[j-k] + arrange(cnt[i], k+1));
			f[j] = mn;
		}
}

int main()
{
	scanf("%d%d%d", &N, &M, &K);
	int t;
	for (int i = 1; i<=N; ++i)
	{
		scanf("%d", &t);
		cnt[t] ++;
	}
	BackPack();
	cout << f[K] << '\n';
	return 0;
}


最后一题,给定一棵树,树上有若干关键点,求从树上每个点开始走完所有关键点的最短路径。N在五十万。

这题我考场上基本一眼看出正解,但是没时间了,早知道这题分值这么大我就先做这题了。。

正解给的是什么连接关键点的路径减去最长链上的路径什么的,比较巧妙。

我想的是从这题本身出发,强行上树DP。具体实现有点像K辆铲雪车问题和最长链问题的合集。

f[i,1]表示从i出发,走完i为根的子树中的所有关键点,最后回到i的最短路径。

f[i,0]表示从i出发,走完i为根的子树中的所有关键点,最后停在子树中的最短路径,同时come[i]记录这个值从哪个子树来的。

f[i,2]意义和f[i,0]类似,只不过是次短路径,切与f[i,0]来自不同的子节点。

g[i,1]和g[i,0],分别表示从i开始,走完其父亲边通向的所有关键点,最后是否返回i节点的最短路径。

转移的时候真的比较麻烦,方程也不是特别好描述。。特别是g数组的转移,一定要多考虑细节。

最后答案,对于每个i,取(f[i,0]+g[i,1])和(f[i,1]+g[i,0])之间的较小值即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
const int MAXN = 500010;

void get(int &r) {
	char c; r = 0;
	do c=getchar(); while (c<'0'||c>'9');
	do r=r*10+c-'0',c=getchar(); while (c>='0'&&c<='9');
}

struct Node {
	int to, d; Node*next;
}Edge[MAXN*2], *ecnt=Edge, *adj[MAXN];
void adde(int a, int b, int c) {
	(++ecnt)->to = b;
	ecnt->d = c;
	ecnt->next = adj[a];
	adj[a] = ecnt;
}
int N, K;

LL f[MAXN][3], g[MAXN][2];
int come[MAXN];
bool must[MAXN];
bool need[MAXN], need1[MAXN];
int needn[MAXN];
int fa[MAXN], fal[MAXN];

void predfs(int u)
{
	if (must[u])
		need1[u] = need[u] = 1;
	for (Node*p = adj[u]; p; p=p->next)
	{
		if (p->to == fa[u]) continue;
		fa[p->to] = u;
		fal[p->to] = p->d;
		if (need1[fa[u]]) need1[u] = 1;
		predfs(p->to);
		if (need[p->to]) need[u] = 1;
		needn[u] += need[p->to];
	}
}

void dfs1(int u)
{
	int mx1 = 0, mx2 = 0, t;
	for (Node*p = adj[u]; p; p=p->next)
	{
		if (p->to == fa[u] || !need[p->to]) continue;
		dfs1(p->to);
		t = (p->d) - f[p->to][0] + f[p->to][1];
		if (t > mx1) mx2 = mx1, mx1 = t, come[u] = p->to;
		else if (t > mx2) mx2 = t;
		f[u][1] += f[p->to][1] + 2*p->d;
	}
	f[u][0] = f[u][1] - mx1;
	f[u][2] = f[u][1] - mx2;
}

void dfs2(int u)
{
	need1[u] = need1[fa[u]] || (needn[fa[u]]-need[u])>0;
	if (need1[u])
	{
		g[u][1] = g[fa[u]][1] + f[fa[u]][1] - f[u][1];
		g[u][0] = g[fa[u]][0] + f[fa[u]][1] - f[u][1] - fal[u]*need[u];
		if (u==come[fa[u]])
			g[u][0] = Min(g[u][0], g[fa[u]][1] + f[fa[u]][2] - f[u][1] - fal[u]);
		else
			g[u][0] = Min(g[u][0], g[fa[u]][1] + f[fa[u]][0] - (f[u][1]+fal[u])*need[u]);
		if (!need[u])
			g[u][1] += 2 * fal[u], g[u][0] += fal[u];
	}
	for (Node*p = adj[u]; p; p=p->next)
		if (p->to != fa[u])
			dfs2(p->to);
}

int main()
{
	get(N), get(K);
	int a, b, c;
	for (int i = 1; i<N; ++i) {
		get(a), get(b), get(c);
		adde(a, b, c); adde(b, a, c);
	}
	for (int i = 1; i<=K; ++i)
		get(a), must[a] = 1;
	predfs(1);
	dfs1(1);
	dfs2(1);
	for (int i = 1; i<=N; ++i)
		cout << Min(g[i][0]+f[i][1], g[i][1]+f[i][0]) << '\n';
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值