前言
总结的顺序什么的不重要了,各种模拟赛的总结能补就补吧。
数学课
输入样例
3
2 2 3
输出样例
15
题解(贪心)
比较水的一个贪心。明显大数放在前面会更好,不然只会乘更大的数是最后结果更大。写成数学语言就是:假设只有a,b,c三个数,先合并a,b,然后再与c合并,就得到
时间
代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <iostream>
#define MAXN 222
#define Mod 1000000007
using namespace std;
typedef long long LL;
int n;
LL a[MAXN], ans;
bool cmp(LL A, LL B){
return A > B;
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
sort(a+1, a+n+1, cmp);
ans = (a[1] * a[2] % Mod + 1) % Mod;
for(int i = 3; i <= n; i++) ans = (ans * a[i] % Mod + 1) % Mod;
printf("%lld\n", ans);
return 0;
}
莎夏和数列
输入样例
5 4
1 1 2 1 1
2 1 5
1 2 4 2
2 2 4
2 1 5
输出样例
5
7
9
题解(线段树+矩阵乘法+快速幂)
本题是一道数据结构题。作为一道货真价实的数据结构题,这道题思维难度不高但比较难写。首先对于先通过矩阵快速幂求出斐波那契数列并将它的矩阵形式保存在线段树中并维护矩阵的和。对于修改操作就是将斐波那契数列的一些下标同时向右移固定的一段。那么求出斐波那契的变换矩阵将同时自乘同样的幂。于是我们直接在修改的区间打一个懒惰标记,记录一个矩阵代表变换矩阵(1110)的幂后的结果,然后区间的每个矩阵都将乘这个矩阵并加起来。由于矩阵乘法神奇的有分配律,所以可以先求和,直接用和去乘。然后由于乘法结合律,懒惰标记也可以合并。查询就直接询问矩阵的和,询问(an+1an)的和矩阵的(1,0)就行了。题目就完全变成线段树的基本操作了。
这题我很快就想到了,但编了2h+,最合还是没有调出来,最后无奈交了暴力(QAQ)。问题主要在于我前面浪费了一些时间,编程习惯不好,一开始直接将线段树开成了矩阵,值和标记搞来搞去,很烦。最后我改变写的方法,在结构体里套结构体,看上去清楚多了(我一开始忘了结构体能套结构体了。。)。最后发现自己超时6个点因为懒惰标记记了数字,多了一个log,改成矩阵后,加法又忘了改成乘法,最后求答案又忘了取模,一波三折才通过此题,我对自己的线段树很自信,没想到诸多细节却没有处理好,这是很大的一个教训,严重影响了我的得分,浪费了做第三题的时间,搞的分数完全不像样。明明是会做的题,为什么分数却拿不到?这是十分致命的!
时间复杂度O(nlog(ai∗n)∗8)。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#define Mod 1000000007
#define MAXN 100010
using namespace std;
typedef long long LL;
int n, m;
LL num[MAXN];
struct Mat{
LL a[3][3];
int Row, Line;
void Clear(){
memset(a, 0, sizeof(a));
}
friend Mat operator + (Mat A, Mat B){
for(int i = 0; i < A.Row; i++)
for(int j = 0; j < A.Line; j++) A.a[i][j] = (A.a[i][j] + B.a[i][j]) % Mod;
return A;
}
friend Mat operator * (Mat A, Mat B){
Mat C; C.Clear();
C.Row = A.Row; C.Line = B.Line;
for(int i = 0; i < A.Row; i++)
for(int j = 0; j < A.Line; j++)
for(int k = 0; k < B.Line; k++)
C.a[i][k] = (C.a[i][k] + A.a[i][j] * B.a[j][k] % Mod) % Mod;
return C;
}
};
struct Tnode{
Mat val, add;
}tree[MAXN<<2];
Mat Pow(Mat A, LL x){
Mat B;
B.Row = B.Line = 2;
B.a[0][0] = B.a[0][1] = B.a[1][0] = 1; B.a[1][1] = 0;
while(x){
if(x & 1) A = B * A;
B = B * B;
x >>= 1;
}
return A;
}
void build(int root, int L, int R){
if(L == R){
Mat A;
A.a[0][0] = A.a[1][0] = 1;
A.Row = 2; A.Line = 1;
tree[root].val = Pow(A, num[L]-1);
A.a[0][0] = A.a[1][1] = 1; A.a[0][1] = A.a[1][0] = 0;
A.Row = 2; A.Line = 2;
tree[root].add = A;
return;
}
int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
build(Lson, L, mid);
build(Rson, mid+1, R);
tree[root].val = tree[Lson].val + tree[Rson].val;
tree[root].add.Row = tree[root].add.Line = 2;
tree[root].add.a[0][0] = tree[root].add.a[1][1] = 1;
tree[root].add.a[0][1] = tree[root].add.a[1][0] = 0;
}
void Down(int root, int Lson, int Rson){
tree[Lson].val = tree[root].add * tree[Lson].val;
tree[Lson].add = tree[root].add * tree[Lson].add;
tree[Rson].val = tree[root].add * tree[Rson].val;
tree[Rson].add = tree[root].add * tree[Rson].add;
tree[root].add.a[0][0] = tree[root].add.a[1][1] = 1;
tree[root].add.a[0][1] = tree[root].add.a[1][0] = 0;
}
void update(int root, int L, int R, int x, int y, Mat v){
if(x > R || y < L) return;
if(x <= L && y >= R){
tree[root].val = v * tree[root].val;
tree[root].add = v * tree[root].add;
return;
}
int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
Down(root, Lson, Rson);
update(Lson, L, mid, x, y, v);
update(Rson, mid+1, R, x, y, v);
tree[root].val = tree[Lson].val + tree[Rson].val;
}
LL query(int root, int L, int R, int x, int y){
if(x > R || y < L) return 0;
if(x <= L && y >= R) return tree[root].val.a[1][0];
int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
Down(root, Lson, Rson);
LL temp1 = query(Lson, L, mid, x, y);
LL temp2 = query(Rson, mid+1, R, x, y);
return (temp1 + temp2) % Mod;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", &num[i]);
build(1, 1, n);
int op, l, r; LL x;
for(int i = 1; i <= m; i++){
scanf("%d", &op);
if(op == 1){
scanf("%d%d%lld", &l, &r, &x);
Mat A;
A.Row = A.Line = 2;
A.a[0][0] = A.a[0][1] = A.a[1][0] = 1; A.a[1][1] = 0;
A = Pow(A, x-1);
update(1, 1, n, l, r, A);
}
else{
scanf("%d%d", &l, &r);
printf("%lld\n", query(1, 1, n, l, r));
}
}
return 0;
}
巡逻
输入样例
8 2
1 2
3 1
3 4
5 3
7 5
8 5
5 6
输出样例
10
题解(找树的直径+树形dp)
这题30分超简单,第二题实在弃疗了我就花了5min左右打了第三题的暴力。比赛完后发现满分也不难。首先题目跟出发点没啥关系,就是直接搞个最长的环最省时间,因为环上走一次,其他边要走两次。首先K=1直接找树的直径,统计长的为d,答案就是2*n-d,然后多了一个环,我一开始就想分类为相交与不相交分开处理。其实不用,我们将第一次选的直径上的边标记为-1就可以同时处理两种情况了。因为第一次取了第二次去相当于抵消了就没取,最后一样的方法算答案即可。这种取反的思想在数据结构中很常见。
树的直径众所周知有两种求法,一种是两遍dfs,另一种是一遍dfs树形dp,记录最长的向下路径和次长的。一开始可以采用第一种方法并记前驱标记那条直径,后面由于出现了负权边就只能树形dp了,因为有些边是可以不取的,而前者搞不定这种情况(坑点),然后这题的时间复杂度O(n)。
话说这题的80分和90分是什么鬼呢。。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#define N 100010
#define oo 0x7fffffff
using namespace std;
int n, K, ans, head_p[N], cur = -1, dis1[N], dis2[N], pre_v[N], pre_e[N], f[N][2];
struct Tadj{int next, obj, len;} Edg[N<<1];
void Insert(int a, int b){
Edg[++cur].next = head_p[a];
Edg[cur].obj = b;
Edg[cur].len = 1;
head_p[a] = cur;
}
void dfs1(int root, int fa){
for(int i = head_p[root]; ~ i; i = Edg[i].next){
int v = Edg[i].obj;
if(v == fa) continue;
dis1[v] = dis1[root] + 1;
dfs1(v, root);
}
}
void dfs2(int root, int fa){
for(int i = head_p[root]; ~ i; i = Edg[i].next){
int v = Edg[i].obj;
if(v == fa) continue;
dis2[v] = dis2[root] + 1;
pre_v[v] = root;
pre_e[v] = i;
dfs2(v, root);
}
}
void Dp(int root, int fa){
for(int i = head_p[root]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, l = Edg[i].len;
if(v == fa) continue;
Dp(v, root);
if(f[v][0] + l > f[root][0]){
f[root][1] = f[root][0];
f[root][0] = f[v][0] + l;
}
else if(f[v][0] + l > f[root][1]) f[root][1] = f[v][0] + l;
}
}
int main(){
scanf("%d%d", &n, &K);
for(int i = 1; i <= n; i++) head_p[i] = -1;
for(int i = 1, a, b; i < n; i++){
scanf("%d%d", &a, &b);
Insert(a, b); Insert(b, a);
}
int M1, p1, M2, p2; M1 = M2 = 0;
dfs1(1, 0);
for(int i = 1; i <= n; i++) if(dis1[i] > M1) M1 = dis1[i], p1 = i;
dfs2(p1, 0);
for(int i = 1; i <= n; i++) if(dis2[i] > M2) M2 = dis2[i], p2 = i;
ans += M2 + 1;
if(K == 2){
for(int i = p2; i != p1; i = pre_v[i]) Edg[pre_e[i]].len = Edg[pre_e[i]^1].len = -1;
Dp(1, 0);
M2 = 0;
for(int i = 1; i <= n; i++) M2 = max(M2, f[i][0] + f[i][1]);
ans += M2 + 1;
}
printf("%d\n", ((n+K-1) << 1) - ans);
return 0;
}
总结
这次考得真是一大败笔。主要败在第二题上。由于我一开始想到了方法所以感到不紧不慢,结果最后一刻也搞不定。遇到考试时策略很重要,应该看完所有题目,都想一想在动手。遇到题目,特别是像第二题这种码农题,一定要先想好框架,不要犯因为代码写得挫搞的调不出来的错误,不能单步跟踪就更要注意代码风格了。总之题目应该想清楚再编,像忘了Mod这种错误再犯就去死吧。Think twice, code once。平时打好基础,自己脑子不好没办法,平时又不勤奋,没有量变哪来的质变呢。拼一把吧。
旋转吧!雪月花。