今天花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; }