noip模拟赛2总结

#(补总结)

#坑中集训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     }

猜你喜欢

转载自www.cnblogs.com/Conless/p/11327240.html