#(补总结)
#坑中集训day8
今天是考试的一天啊~
4题的noip senior模拟赛 3个小时
我这种垃圾还是一如既往地被吊打~
好吧不说废(zhuang)话(ruo)
T1 数学课
容易看出这题是要选最小的结果,即求出所有ai * aj + 1的最小值
由于(ai * aj + 1) * ak + 1 = ai*aj*ak + ak + 1, 因此对于任意三个ai, aj, ak, 选择其中最大的两个数进行优先相乘,得到的最终结果都是最小的,同理,由于新得到的数仍然将加入队列中,所以每一次都应该选择最大的两个数相乘
以下是比赛程序
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 5 #define MOD 1000000007 6 7 using namespace std; 8 9 int n, a; 10 long long x1, x2, res; 11 priority_queue<long long> q; // 最开始脑抽当成求最大值了,搞了个 12 // priorty_queue<int, vector<int>, greater<int> > 13 int main() // 其实好像可以要直接插入队尾…… 14 { 15 // freopen("a.in", "r", stdin); 16 // freopen("a.out", "w", stdout); 17 scanf("%d", &n); 18 for (int i = 1; i <= n; i++) 19 { 20 scanf("%d", &a); 21 q.push(a); 22 } 23 while (q.size() > 1) 24 { 25 x1 = q.top(); 26 q.pop(); 27 x2 = q.top(); 28 q.pop(); 29 res = (x1 * x2 + 1) % MOD; //避免乘法过程中爆直接开long long就好了…… 30 q.push(res); 31 } 32 printf("%lld", q.top()); 33 return 0; 34 }
不过注意注意注意!
这个程序是错的!
在不取模的情况下,两数相乘+1肯定是新得到的数中最大的,但是如果在取模后插入优先队列,并不能保证下一个直接选择该数,即有可能得到的结果并非最小(可是数据太水居然过了)
因此应该直接将队列中的最大数与原先得到的数进行相乘
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 5 #define MOD 1000000007 6 7 using namespace std; 8 9 int n, a; 10 long long x1, x2, res; 11 priority_queue<long long> q; 12 13 int main() 14 { 15 // freopen("a.in", "r", stdin); 16 // freopen("a.out", "w", stdout); 17 scanf("%d", &n); 18 for (int i = 1; i <= n; i++) 19 { 20 scanf("%d", &a); 21 q.push(a); 22 } 23 res = q.top(); 24 q.pop(); 25 while (q.size()) 26 { 27 x1 = q.top(); 28 q.pop(); 29 res = (x1 * res + 1) % MOD; 30 } 31 printf("%lld", res); 32 return 0; 33 }
所以签到题我其实应该wa掉的……
咳咳赶紧来看第二题(掩饰尴尬
T2 数列分段
直接贴地址吧https://www.luogu.org/problem/P1182
是一道比较常规的二分答案
当分成k段(1<=k<=m),每段最大长度为l时,对于最大长度>=l的答案,肯定也可行,符合决策单调性
那直接写check就行了吧……
1 bool check(int k) 2 { 3 int i, j, tot = 0; 4 for (i = 1, j = 1; i <= n && j <= m; i++) 5 { 6 tot += a[i]; 7 if (tot > k) 8 { 9 tot = a[i]; 10 if (a[i] > k) 11 return 0; 12 j++; 13 } 14 } 15 if (i == n + 1 && j <= m) 16 return 1; 17 return 0; 18 }
T3 巡逻
贴地址https://www.luogu.org/problem/P3629
是一道APIO的题/汗
也是在我这种fw看来比赛最难的一题……
最开始还以为是一个有向无环图……差点准备开始贴样例的时候突然看到n-1行输入(眼睛不要可以捐给有需要的人)
因此在认真思(fa)考(dai)之后还是决定开始自己的骗分旅程
首先分类讨论:
1.k=1时
这时我们发现在添边之前,在一棵树上由于不存在环,因此每一条边都被遍历了两次(在字节点和父节点之间来回),而添加一条边,可以使一个点fi与子树中的某一节点,两点之间的路径需要且仅需要经过一次,而由于在不添边前,以任意节点作为父节点结果都是一样的,所以只需要寻找树上最长链即可
1 if (k == 1) 2 { 3 dfs(1, 0); 4 printf("%d", 2 * (n - 1) - maxlen + 1); 5 }
dfs的实现方式:
1 //标注k=2的是对k=2的情况服务的步骤 2 int maxlen, max1, max2; 3 int f1[MAXN], f2[MAXN]; 4 int n1[MAXN], n2[MAXN]; //k = 2 记录路径 5 void dfs(int now, int last) 6 { 7 hi[now] = hi[last] + 1; 8 fat[now] = last; //k = 2 记录父节点 9 n1[now] = n2[now] = now; 10 for (int i = head[now]; i; i = edge[i].next) 11 { 12 int v = edge[i].v; 13 if (v == last) 14 continue; 15 dfs(v, now); 16 edf[v] = i; 17 if (f1[v] + 1 > f1[now]) 18 { 19 f2[now] = f1[now]; 20 n2[now] = n1[now]; // k = 2 21 f1[now] = f1[v] + 1; 22 n1[now] = n1[v]; // k = 2 23 } 24 else if (f1[v] + 1 > f2[now]) 25 { 26 f2[now] = f1[v] + 1; 27 n2[now] = n1[v]; 28 } 29 } 30 if (f1[now] + f2[now] > maxlen) 31 { 32 maxlen = f1[now] + f2[now]; 33 max1 = n1[now]; // k = 2 34 max2 = n2[now]; // k = 2 35 } 36 }
2.k=2时
APIO的题会让宁这么轻松过掉吗2333
因此又要开始懵逼集锦了hhhh
首先最长链肯定还是要选的,那就需要在其它的链中作出抉择
可以看出,在选择完最长链后,有三条边可以少跑一次
那么如果此时我们选择次长链(2-7)
可以看出,此时的最短路径是1-2-8-5-7-2-1-3-4-3-5-6-5-3-1
就是说原来链2-8可以不必重复经过3条绿边,链2-7不用重复经过3条黄边
但是现在却要重复经过其中共有的2条,即在第二次进行dfs时,这两条边提供的贡献是-1
那我们就可以重新记录价值并根据权值求树上最长链
贴代码
1 else { 2 dfs(1, 0); 3 int now1 = max1, now2 = max2, len1 = 0, len2 = 0; 4 while (hi[now1] < hi[now2]) // 对最长链上的每一条边进行标记 5 { 6 edge[edf[now2]].w = -1; // edf表示每一个节点与父亲节点边的编号 7 now2 = fat[now2]; // fat表示每一个节点的父节点 8 } 9 while (hi[now1] > hi[now2]) 10 { 11 edge[edf[now1]].w = -1; 12 now1 = fat[now1]; 13 } 14 while (now1 != now2) 15 { 16 edge[edf[now2]].w = -1; 17 now2 = fat[now2]; 18 edge[edf[now1]].w = -1; 19 now1 = fat[now1]; 20 } 21 dfs1(1, 0); 22 printf("%d", 2 * (n - 1) - maxlen + 1 - seclen + 1); 23 }
1 int seclen; 2 int dfs1(int now, int last) // 同求最长链的dfs,对权值进行了判断 3 { 4 int s1 = 0, s2 = 0; 5 for (int i = head[now]; i; i = edge[i].next) 6 { 7 int v = edge[i].v; 8 if (v == last) 9 continue; 10 int res = dfs1(v, now); 11 if (res + edge[i].w > s1) 12 { 13 s2 = s1; 14 s1 = res + edge[i].w; 15 } 16 else if (res + edge[i].w > s2) 17 s2 = res + edge[i].w; 18 } 19 seclen = max(seclen, s1 + s2); 20 return s1; 21 }
咳咳可是以上说的k=2的分析我比赛的时候全都没想到
比赛的时候就直接如果该边在最长路径中则直接排除,vis[i]=1
可以证明这种方法的错误性……(可是我就是那么蠢没想到正解)
T4 玩具取名
贴地址https://www.luogu.org/problem/P4290
题意简单来讲就是一个字符串可以由哪些字符通过变换得到
作为一个热爱忽悠(乱捅)的人肯定直接准备打O(nn)暴力(溜了
然后看着len<=200也放弃了状压
但是这两种方法都是基于w i n g向上进行拓展
看到原字符串<=200的长度,可以想到在字符串的基础上向下压缩应该会更优
每一次选择其中的两个字符进行压缩,再一直对所得字符串进行压缩
但是这样搜索仍然会处理很多的重复情况,更难以记录每一次搜索的状态
因此我们就能想到通过dp/记忆化搜索,在原字符串的变形下进行记录
即用f[i][j][k]记录字符串的第i-j位能否用第k个字符表示(1<=i,j<=len, 1<=k<=4),用d[i][j][k]表示第k个字符能否变为"ij“(1<=i,j,k<=4)
那么便能推出递推方程:
f[i][j][c] = f[i][k][a]&f[k+1][j][b]&d[a][b][c] (i<=k<j, 1<=a,b,c<=4)
所以直接上代码吧
1 for (int l = 2; l <= len; l++) // l枚举区间长度 2 { 3 for (int i = 1; i + l - 1 <= len; i++) 4 { 5 int j = i + l - 1; 6 for (int k = i; k < j; k++) 7 for (int c1 = 1; c1 <= 4; c1++) 8 for (int c2 = 1; c2 <= 4; c2++) 9 if (f[i][k][c1] && f[k + 1][j][c2]) 10 for (int c3 = 1; c3 <= 4; c3++) 11 if (d[c1][c2][c3]) 12 f[i][j][c3] = 1; 13 } 14 }