SDOI 2017 Round1 题解

Day1

Problem 1:数字表格

题目描述

Doris刚刚学习了fibonacci数列。用 f[i] 表示数列的第 i 项,那么

f[0]=0
f[1]=1
f[n]=f[n1]+f[n2],n2
Doris用老师的超级计算机生成了一个 n×m 的表格,第 i 行第 j 列的格子中的数是 f[gcd(i,j)] ,其中 gcd(i,j) 表示 i,j 的最大公约数。
Doris的表格中共有 n×m 个数,她想知道这些数的乘积是多少。
答案对 109+7 取模。

输入格式

有多组测试数据。
第一个一个数 T ,表示数据组数。
接下来 T 行,每行两个数 n,m

输出格式

输出 T 行,第 i 行的数是第 i 组数据的结果

样例1

input

3
2 3
4 5
6 7

output

1
6
960

限制与约定

10% 的数据, 1n,m100
30% 的数据, 1n,m1000
另外存在 30% 的数据, T3
100% 的数据, T1000,1n,m106

时间限制:5s
内存限制:128M

题解

ans=i=1nj=1mfib[gcd(i,j)]
ans=g=1min(n,m)fib[g]ni=1mj=1[gcd(i,j)=g]
进行正常的莫比乌斯反演,得到:
ans=g=1min(n,m)fib[g]min(n,m)gd=1μ(d)×ndg×mdg
D=dg ,则
ans=D=1min(n,m)g|Dfib[g]μ(Dg)×nD×mD
F(D)=g|Dfib[g]μ(Dg)

F(D) 可以 nlogn 预处理。
ans=D=1min(n,m)F(D)nD×mD

nD×mD 取值只有 n+m 种,进行分块。
所以总的复杂度为: O(nlogn+T×(n+m)logmod)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;
const int maxn = 1000010, p = 1e9+7;
ll fib[maxn], Nfib[maxn], F[maxn], NF[maxn];
int prime[maxn], mu[maxn], pcnt, T, n, m, MX;
bool is[maxn];

ll pow(ll x, ll y){
    ll res = 1;
    for( ; y; y >>= 1, x = (x*x) % p) if(y & 1) res = (res * x) % p;
    return res;
}

void pre(){
    is[1] = mu[1] = fib[1] = NF[0] = 1;
    for(int i = 2; i <= MX; i ++){
        if(!is[i]) mu[i] = -1, prime[++ pcnt] = i;
        for(int j = 1; i * prime[j] <= MX; j ++){
            is[i * prime[j]] = 1;
            if(i % prime[j] == 0) break;
            else mu[i*prime[j]] = - mu[i];
        }
    }
    for(int i = 2; i <= MX; i ++) fib[i] = (fib[i-1] + fib[i-2]) % p;
    for(int i = 1; i <= MX; i ++) Nfib[i] = pow(fib[i], p-2);
    for(int i = 1; i <= MX; i ++) F[i] = 1;
    for(int i = 1; i <= MX; i ++)
        for(int j = 1; i*j <= MX; j ++)
            if(mu[j]) F[i*j] = F[i*j] * (mu[j] == 1 ? fib[i] : Nfib[i]) % p;
    for(int i = 2; i <= MX; i ++) F[i] = (F[i] * F[i-1]) % p;
    for(int i = 1; i <= MX; i ++) NF[i] = pow(F[i], p-2);
    return;
}

int main(){
    MX = 1000000; pre();
    scanf("%d", &T);
    while(T --){
        scanf("%d%d", &n, &m);
        if(n > m) swap(n, m);
        int last;
        ll ans = 1;
        for(int i = 1; i <= n; i = last+1){
            last = min(n/(n/i), m/(m/i));
            ans = (ans * pow(((F[last] * NF[i-1]) % p + p) % p, 1LL * (n/i) * (m/i) % (p-1))) % p;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

Problem 2:树点涂色

题目描述

Bob有一棵 n 个点的有根树,其中1号点是根节点。Bob在每个点上涂了颜色,并且每个点上的颜色不同。
定义一条路径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色。
Bob可能会进行这几种操作:

  • 1 x

把点 x 到根节点的路径上所有的点染上一种没有用过的新颜色。

  • 2 x y

x y 的路径的权值。

  • 3 x

在以 x 为根的子树中选择一个点,使得这个点到根节点的路径权值最大,求最大权值。
Bob一共会进行 m 次操作

输入格式

第一行两个数 n,m
接下来 n1 行,每行两个数 a,b ,表示 a b 之间有一条边。
接下来 m 行,表示操作,格式见题目描述

输出格式

每当出现2,3操作,输出一行。
如果是2操作,输出一个数表示路径的权值
如果是3操作,输出一个数表示权值的最大值

样例1

input

5 6
1 2
2 3
3 4
3 5
2 4 5
3 3
1 4
2 4 5
1 5
2 4 5

output

3
4
2
2

限制与约定

共10个测试点
测试点1, 1n,m1000
测试点2、3,没有2操作
测试点4、5,没有3操作
测试点6,树的生成方式是,对于 i(2in) ,在1到 i1 中随机选一个点作为i的父节点。
测试点7, 1n,m50000
测试点8, 1n50000
测试点9,10,无特殊限制
对所有数据, 1n105 1m105

时间限制:1s
空间限制:128MB

题解

先对树进行树链剖分,线段树每个节点维护该点到跟的路径上的权值。
LCT Access 维护覆盖新颜色对权值的影响。
一开始整棵树由轻链连接,每一次修改颜色的的操作是把这个点到跟的路径上的所有点变成一种没有出现过的颜色,对应到 LCT 中,就相当于对一个点进行 Access 的操作,而一个点的答案就是这个点到跟经过的轻链个数+1,一条虚边改为实边的操作相当于线段树上对子树答案-1,一条实边改为虚边相当于把线段树上子树的答案+1.

第一种操作,我们 Access 到点 x 的路径,每一个路径如果发生了实边与虚边的变化就更新答案。

第二种操作,所有的操作都可以总结为下面这张图:

  • A区域内的颜色在左右计算的时候都会被计算一次,所以计算了两次。
  • B区域内的颜色在左右计算的时候都会被计算一次,所以计算了两次。
  • C区域内的颜色只被左边计算了一次。
  • D区域内的颜色只被右边计算了一次。

因为B区域被减了两次,但实际需要被算入答案一次,所以答案是两个询问点的权值和减去两倍的 LCA 的权值 +1

第三种操作,就是线段树的区间最大值。

时间复杂度: O((n+mlogn)×logn)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 100010;

struct node{
    int id;
    node *pre, *ch[2];
    bool is_root(){return pre->ch[0] != this && pre->ch[1] != this;}
    void set_ch(int wh, node *child);
    int wh(){return pre->ch[0] == this ? 0 : 1;}
}po[maxn], *null;
void node::set_ch(int wh, node *child){
    ch[wh] = child;
    if(child != null) child->pre = this;
}
int n, m, ct;
int head[maxn], to[maxn<<1], nxt[maxn<<1], cnt;
int dep[maxn], sz[maxn], fa[maxn], pos[maxn], q[maxn], st[maxn];
int son[maxn], dfn[maxn], tp[maxn];
bool vis[maxn];
int tree[maxn*3], lazy[maxn*3];
void add(int a, int b){
    nxt[++ cnt] = head[a], to[head[a] = cnt] = b;
}
#define mid ((l+r)>>1)
#define lch ((now<<1))
#define rch ((now<<1)|1)
void build(int now, int l, int r){
    if(l == r){
        tree[now] = dep[dfn[l]];
        return;
    }
    build(lch, l, mid);
    build(rch, mid+1, r);
    tree[now] = max(tree[lch], tree[rch]);
}
void pre(){
    dep[1] = 1;
    int l = 1, r = 0;
    q[++ r] = 1, vis[1] = 1; 
    while(l <= r){
        int x = q[l ++]; sz[x] = 1;
        for(int i = head[x]; i; i = nxt[i]){
            int u = to[i]; if(vis[u]) continue;
            fa[u] = x, dep[u] = dep[x] + 1;
            q[++ r] = u, vis[u] = 1;
        }
    }
    for(int i = n; i >= 2; i --) sz[fa[q[i]]] += sz[q[i]];
    for(int x = 1; x <= n; x ++){
        for(int i = head[x]; i; i = nxt[i]){
            int u = to[i]; if(u == fa[x]) continue;
            if(sz[son[x]] < sz[u]) son[x] = u;
        }
    }
    int top = 0;
    st[++ top] = 1, tp[1] = 1;
    while(top){
        int x = st[top --];
        pos[x] = ++ ct, dfn[ct] = x;
        for(int i = head[x]; i; i = nxt[i]){
            int u = to[i]; if(u == son[x] || u == fa[x]) continue;
            st[++ top] = u, tp[u] = u;
        }
        if(son[x]) st[++ top] = son[x], tp[son[x]] = tp[x];
    }
    for(int i = 1; i <= n; i ++){
        po[i].ch[0] = po[i].ch[1] = null;
        po[i].id = i, po[i].pre = &po[fa[i]];
    }
    build(1, 1, n);
}
void down(int now, int l, int r){
    if(!lazy[now]) return;
    int &z = lazy[now];
    tree[lch] += z, tree[rch] += z;
    lazy[lch] += z, lazy[rch] += z;
    z = 0;
}
void modify(int now, int l, int r, int pos1, int pos2, int c){
    if(l == pos1 && r == pos2){
        lazy[now] += c;
        tree[now] += c;
        return;
    }down(now, l, r);
    if(pos2 <= mid) modify(lch, l, mid, pos1, pos2, c);
    else if(pos1 >= mid+1) modify(rch, mid+1, r, pos1, pos2, c);
    else modify(lch, l, mid, pos1, mid, c), modify(rch, mid+1, r, mid+1, pos2, c);
    tree[now] = max(tree[lch], tree[rch]);
}
int que(int now, int l, int r, int pos1, int pos2){
    if(l == pos1 && r == pos2) return tree[now];
    down(now, l, r);
    if(pos2 <= mid) return que(lch, l, mid, pos1, pos2);
    else if(pos1 >= mid+1) return que(rch, mid+1, r, pos1, pos2);
    else return max(que(lch, l, mid, pos1, mid), que(rch, mid+1, r, mid+1, pos2));
}
int Lca(int x, int y){
    if(x == y) return x;
    while(tp[x] != tp[y]){
        if(dep[tp[x]] < dep[tp[y]]) swap(x, y);
        x = fa[tp[x]];
    }
    return dep[x] < dep[y] ? x : y;
}
void rotate(node *now){
    node *fa = now->pre, *gra = fa->pre;
    int wh = now->wh();
    if(!fa->is_root()) gra->ch[gra->ch[0] == fa ? 0 : 1] = now;
    fa->set_ch(wh, now->ch[wh^1]);
    now->set_ch(wh^1, fa), now->pre = gra;
}
void splay(node *now){
    for( ; !now->is_root(); rotate(now))
        if(!now->pre->is_root())
            now->wh() == now->pre->wh() ? rotate(now->pre) : rotate(now);
}
void access(node *x){
    for(node *i = null; x != null; i = x, x = x->pre){
        splay(x);
        if(x->ch[1] != i){
            if(x->ch[1] != null){
                node *le = x->ch[1];
                while(le->ch[0] != null) le = le->ch[0];
                int ID = le->id;
                modify(1, 1, n, pos[ID], pos[ID] + sz[ID] - 1, 1);
            }
            x->set_ch(1, i);
            node *le = i;
            while(le->ch[0] != null) le = le->ch[0];
            if(i != null) modify(1, 1, n, pos[le->id], pos[le->id]+sz[le->id] - 1, -1);
        }
    }
}
int main(){
    null = po;
    null->pre = null->ch[0] = null->ch[1] = null;
    scanf("%d%d", &n, &m);
    for(int i = 1, x, y; i < n; i ++){
        scanf("%d%d", &x, &y);
        add(x, y), add(y, x);
    }
    pre();
    for(int i = 1; i <= m; i ++){
        int op, x, y; scanf("%d", &op);
        if(op == 1){
            scanf("%d", &x);
            access(&po[x]);
        }else if(op == 2){
            scanf("%d%d", &x, &y);
            int lca = Lca(x, y);
            printf("%d\n", que(1, 1, n, pos[x], pos[x]) + que(1, 1, n, pos[y], pos[y]) - 2 * que(1, 1, n, pos[lca], pos[lca]) + 1);
        }else{
            scanf("%d", &x);
            printf("%d\n", que(1, 1, n, pos[x], pos[x] + sz[x] - 1));
        }
    }
    return 0;
}

Problem 3:序列计数

题目描述

Alice想要得到一个长度为 n 的序列,序列中的数都是不超过 m 的正整数,而且这 n 个数的和是 p 的倍数。
Alice还希望,这 n 个数中,至少有一个数是质数。
Alice想知道,有多少个序列满足她的要求。

输入格式

一行三个数, n,m,p

输出格式

一行一个数,满足Alice的要求的序列数量,答案对 20170408 取模。

样例1

input

3 5 3

input

33

约定与限制

20% 的数据, 1n,m100
50% 的数据, 1m100
80% 的数据, 1m106
100% 的数据, 1n109,1m2×107,1p100

时间限制:3s
空间限制:128MB

题解

首先问题可以转换为求所有数的答案减去非质数的答案。
dp[i][j] 表示使用了前 i 个数, %p=j 的方案数,那么 dp[i][j]+=dp[i1][(jk)%p]
这样就可以用矩阵快速幂了,转移矩阵的第 i 行,第 j 列的一个数表示在上个矩阵的第1行 j 列的数可以转移给这一个矩阵的第1行第 i 列多少次。
这个次数就是说有数中 %p=(ji)%p 的数的个数。
然后可以线性筛法预处理这个个数,在 O(p2) 的复杂度下完成。
时间复杂度: O(p2+p3logn)

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>

using namespace std;
const int maxn = 20000010;
const int mod = 20170408;
int prime[maxn], pcnt;
bool is[maxn];
int nump[110], num[110], L, MX;
int n, m, p;
void pre(){
    is[1] = 1;
    num[1] = nump[1] = 1;
    for(int i = 2; i <= MX; i ++){
        if(!is[i]) prime[++ pcnt] = i, nump[i%p] ++;
        for(int j = 1; i * prime[j] <= MX; j ++){
            int w = i * prime[j];
            is[w] = 1, num[w%p] ++, nump[w%p] ++;
            if(i % prime[j] == 0) break;
        }
    }
}

struct Matrix{
    long long a[101][101];
    Matrix(){memset(a, 0, sizeof(a));}
    void init(){for(int i = 0; i < L; i ++) a[i][i] = 1;}
    Matrix operator * (const Matrix &t) const{
        Matrix res;
        for(int i = 0; i < L; i ++)
            for(int j = 0; j < L; j ++)
                for(int k = 0; k < L; k ++)
                    res.a[i][j] = (res.a[i][j] + a[i][k] * t.a[k][j] % mod) % mod;
        return res;
    }
}A, B, T1, T2;

Matrix pow(Matrix x, int y){
    Matrix res; res.init();
    for( ; y; y >>= 1, x = x*x) if(y & 1) res = res * x;
    return res;
}

int main(){
    scanf("%d%d%d", &n, &m, &p), L = p, MX = m;
    pre(); A.a[0][0] = B.a[0][0] = 1;
    for(int i = 0; i < L; i ++){
        for(int j = 0; j < L; j ++){
            T1.a[i][j] = (T1.a[i][j] + nump[((j-i)%p+p)%p]) % mod;
            T2.a[i][j] = (T2.a[i][j] + num[((j-i)%p+p)%p]) % mod;
        }
    }
    A = A * pow(T1, n);
    B = B * pow(T2, n);
    printf("%d", ((A.a[0][0] - B.a[0][0]) % mod + mod) % mod);
    return 0;
}

Day2

Problem 1:新生舞会

题目描述

学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴。
n 个男生和 n 个女生参加舞会买一个男生和一个女生一起跳舞,互为舞伴。
Cathy收集了这些同学之间的关系,比如两个人之前认识没计算得出 ai,j ,表示第 i 个男生和第 j 个女生一起跳舞时他们的喜悦程度。
Cathy还需要考虑两个人一起跳舞是否方便,比如身高体重差别会不会太大,计算得出 bi,j ,表示第 i 个男生和第 j 个女生一起跳舞时的不协调程度。
当然,还需要考虑很多其他问题。
Cathy想先用一个程序通过 ai,j bi,j 求出一种方案,再手动对方案进行微调。
Cathy找到你,希望你帮她写那个程序。
一个方案中有 n 对舞伴,假设没对舞伴的喜悦程度分别是 a1,a2,...,an ,假设每对舞伴的不协调程度分别是 b1,b2,...,bn 。令

C=a1+a2+...+anb1+b2+...+bn

Cathy希望 C 值最大。

输入格式

第一行一个整数 n
接下来 n 行,每行 n 个整数,第 i 行第 j 个数表示 ai,j
接下来 n 行,每行 n 个整数,第 i 行第 j 个数表示 bi,j

输出格式

一行一个数,表示 C 的最大值。四舍五入保留 6 位小数,选手输出的小数需要与标准输出相等。

样例1

input

3
19 17 16
25 24 23
35 36 31
9 5 6
3 4 2
7 8 9

output

5.357143

限制与约定

对于 10% 的数据, 1n5
对于 40% 的数据, 1n18
另外存在 20% 的数据, bi,j=1 ​​;
对于 100% 的数据, 1n100,1ai,j104,1bi,j104

时间限制:1s
内存限制:128M

题解

把所有的分母乘到右边,在减回来,这样可以二分C的取值,判断当前式子的最大值是否大于0,问题转换为验证一个 C 的值是否合法,这就是经典的二分图最大权匹配,用 SPFA 费用流解决。
还要加上有理有据的常数优化才可以通过。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;
const int maxn = 410;
const int maxm = 40010;
const int inf = 2e9+1;
int n;
int a[maxn][maxn], b[maxn][maxn], CC;
int head[maxn], nxt[maxm], to[maxm], flow[maxm], mxflow[maxm];
int q[maxm], l, r, epre[maxn], ppre[maxn], S, T, MX, cnt;
double dis[maxn], cost[maxm];
bool vis[maxn];
void add(int a, int b, int c, double d){
    nxt[++ cnt] = head[a], to[head[a] = cnt] = b, flow[cnt] = 0, mxflow[cnt] = c, cost[cnt] = d;
    nxt[++ cnt] = head[b], to[head[b] = cnt] = a, flow[cnt] = 0, mxflow[cnt] = 0, cost[cnt] = -d;
}

bool spfa(){
    for(int i = 0; i <= MX; i ++){
        dis[i] = inf;
        vis[i] = 0;
        epre[i] = ppre[i] = 0;
    }
    l = 1, r = 0, q[++ r] = S, vis[S] = 1, dis[S] = 0;
    while(l <= r){
        int x = q[l ++]; vis[x] = 0;
        for(int i = head[x]; i; i = nxt[i]){
            int u = to[i];
            if(mxflow[i] > flow[i] && dis[u] > dis[x] + cost[i]){
                dis[u] = dis[x] + cost[i];
                epre[u] = i, ppre[u] = x;
                if(vis[u] == 0) q[++ r] = u, vis[u] = 1;
            }
        }
    }
    return dis[T] < inf - 2;
}
bool work(){
    double Cost = 0;
    while(spfa()){
        Cost += dis[T];
        if(Cost > 0) return 0;
        for(int i = T; i != S; i = ppre[i]){
            int e = epre[i];
            flow[e] ++, flow[e^1] --;
        }
    }
    CC = Cost;
    if(Cost > 0) return 0;
    return 1;
}


int main(){
    scanf("%d", &n);
    S = 2*n+1, T = S+1, MX = T+1;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++)
            scanf("%d", &a[i][j]);
    bool ok = 1;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++){
            scanf("%d", &b[i][j]);
            if(b[i][j] != 1) ok = 0;
        }
    if(ok){
        cnt = 1;
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= n; j ++){
                add(i, n+j, 1, -a[i][j]);
            }
        }
        for(int i = 1; i <= n; i ++) add(S, i, 1, 0), add(i+n, T, 1, 0);
        work();
        printf("%.6lf", -1.0 * CC / n);
        return 0;
    }

    double l = 0, r = 10000;

    while(r - l >= 0.00000001){
        double mid = (r+l)/2;
        cnt = 1;
        for(int i = 1; i <= MX; i ++) head[i] = 0;
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= n; j ++){
                add(i, n+j, 1, - a[i][j] + mid * b[i][j]);
            }
        }
        for(int i = 1; i <= n; i ++) add(S, i, 1, 0), add(n+i, T, 1, 0);
        if(work()) l = mid;
        else r = mid;
    }printf("%.6lf", (l+r)/2);
    return 0;
}

Problem 2:硬币游戏

题目描述

周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利。
大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了。
同学们觉得要加强趣味性,所以要找一个同学扔很多很多次硬币,其他同学记录下正反面情况。
用H表示正面朝上,用 T 表示反面朝上,扔很多次硬币后,会得到一个硬币序列。比如 HTT 表示第一次正面朝上,后两次反面朝上。
但扔到什么时候停止呢?大家提议,选出 n 个同学,每个同学猜一个长度为 m 的序列,当某一个同学猜的序列在硬币序列中出现时,就不再扔硬币了,并且这个同学胜利,为了保证只有一个同学胜利,同学们猜的 n 个序列两两不同。
很快, n 个同学猜好序列,然后进入了紧张而又刺激的扔硬币环节。你想知道,如果硬币正反面朝上的概率相同,每个同学胜利的概率是多少。

输入格式

第一行两个整数 n,m
接下里 n 行,每行一个长度为 m 的字符串,表示第 i 个同学猜的序列。

输出格式

输出 n 行,第 i 行表示第 i 个同学胜利的概率。
选手输出与标准输出的绝对误差不超过 106 即视为正确。

样例1

input

3 3
THT
TTH
HTT

output

0.3333333333
0.2500000000
0.4166666667

限制与约定

对于 10% 的数据, 1n,m3
对于 40% 的数据, 1n,m18
对于另外 20% 的数据, n=2
对于 100% 的数据, 1n,m300

时间限制:1s
空间限制:128MB

题解



代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 310;

int n, m;
char cc[maxn][maxn];
long double ec[maxn], a[maxn][maxn];
int f[maxn];
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) scanf("%s", cc[i] + 1);
    ec[0] = 1;
    for(int i = 1; i <= m; i ++) ec[i] = 0.5 * ec[i-1];
    for(int i = 1; i <= n; i ++){
        int p = 0;
        for(int j = 2; j <= m; j ++){
            while(p && cc[i][j] != cc[i][p+1]) p = f[p];
            if(cc[i][j] == cc[i][p+1]) p ++;
            f[j] = p;
        }
        for(int j = 1; j <= n; j ++){
            p = 0;
            for(int k = 1; k <= m; k ++){
                while(p && cc[j][k] != cc[i][p+1]) p = f[p];
                if(cc[j][k] == cc[i][p+1]) p ++;
            }
            for( ; p; p = f[p]) a[i][j] += ec[m - p];
        }
        a[i][n+1] = ec[0];
    }
    for(int i = 1; i <= n; i ++){
        for(int j = i+1; j <= n+1; j ++)
            a[i][j] /= a[i][i];
        a[i][i] = 1;
        for(int j = i+1; j <= n; j ++){
            for(int k = i+1; k <= n+1; k ++)
                a[j][k] -= a[i][k] * a[j][i];
            a[j][i] = 0;
        }
    }
    for(int i = n; i >= 1; i --)
        for(int j = i+1; j <= n; j ++)
            a[i][n+1] -= a[i][j] * a[j][n+1];
    long double sum = 0;
    for(int i = 1; i <= n; i ++) sum += a[i][n+1];
    for(int i = 1; i <= n; i ++) 
        printf("%.10lf\n", (double)((long double)a[i][n+1] / sum));
    return  0;
}

Problem 3:相关分析

题目描述

Frank对天文学非常感兴趣,他经常用望远镜看星星,同时记录下它们的信息,比如亮度、颜色等等,进而估算出星星的距离,半径等等。
Frank不仅喜欢观测,还喜欢分析观测到的数据。他经常分析两个参数之间(比如亮度和半径)是否存在某种关系。
现在Frank要分析参数 X Y 之间的关系。他有 n 组观测数据,第 i 组观测数据记录了 xi yi 。他需要一下几种操作:

  • 1 L R:

用直线拟合第L组到底R组观测数据。用 x¯ 表示这些观测数据中 x 的平均数,用 y¯ 表示这些观测数据中 y 的平均数,即

x¯=1RL+1i=LRxi
y¯=1RL+1i=LRyi

如果直线方程是y=ax+b,那么a应当这样计算:
ab=i=LR(xix¯)(yiy¯)i=LR(xix¯)2=y¯ax¯

你需要帮助Frank计算 a

  • 2 L R S T:

Frank发现测量数据第 L 组到第 R 组数据有误差,对每个 i 满足 LiR xi 需要加上 S yi 需要加上 T

  • 3 L R S T:

Frank发现第 L 组到第 R 组数据需要修改,对于每个 i 满足 LiR xi 需要修改为 (S+i) yi 需要修改为 (T+i)

输入格式

第一行两个数 n m ,表示观测数据组数和操作次数。
接下来一行 n 个数,第 i 个数是 xi
接下来一行 n 个数,第 i 个数是 yi
接下来 m 行,表示操作,格式见题目描述。

输出格式

对于每个 1 操作,输出一行,表示直线斜率 a
选手输出与标准输出的绝对误差或相对误差不超过 105 ​​即为正确。

样例1

input

3 5
1 2 3
1 2 3
1 1 3
2 2 3 -3 2
1 1 2
3 1 2 2 1
1 1 3

input

1.0000000000
-1.5000000000
-0.6153846154

约定与限制

对于 20% 的数据, 1n,m1000
对于另外 20% 的数据没有 3 操作,且 2 操作中 S=0
对于另外 30% 的数据没有 3 操作;
对于 100% 的数据, 1n,m105
对于所有数据, 1LRn,0|S|,|T|105,0|xi|,|yi|105
对于所有数据, 1 操作中不会出现分母为 0 这类特殊情况。

时间限制:1s
空间限制:128MB

题解


将上面计算 a 的公式展开,不难发现只需要维护 x,y 的和, x 的平方和, x×y 的和,平方和的维护可以考虑当 x+1 时值的变化,然后用上面维护的信息可以维护。

对于一个3操作,可以考虑先进行区间赋值为标号本身,再做2操作。
等差数列的和和平方和都可以 O(1) 得到答案。
时间复杂度 O((n+m)logn)

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;
const int maxn = 100010;
int n, m;
double x[maxn], y[maxn];
long double S[maxn];

#define mid ((l+r)>>1)
#define lch ((now<<1))
#define rch ((now<<1)|1)

struct node{
    long double xx, yy, x2, xy;
    long double tgx, tgy;
    int is;
    node(long double a = 0, long double b = 0, long double c = 0, long double d = 0){
        xx = a, yy = b, x2 = c, xy = d;
        tgx = 0, tgy = 0; is = 0;
    }
    node operator + (const node &t) const{
        node res;
        res.xx = xx + t.xx;
        res.yy = yy + t.yy;
        res.x2 = x2 + t.x2;
        res.xy = xy + t.xy;
        return res;
    }
}T[maxn*3];

void update(int now, int l, int r){
    T[now].xx = (T[lch].xx + T[rch].xx);
    T[now].yy = (T[lch].yy + T[rch].yy);
    T[now].x2 = (T[lch].x2 + T[rch].x2);
    T[now].xy = (T[lch].xy + T[rch].xy);
}

void build(int now, int l, int r){
    if(l == r){
        T[now] = node(x[l], y[l], x[l]*x[l], x[l]*y[l]);
        return;
    }
    build(lch, l, mid);
    build(rch, mid+1, r);
    update(now, l, r);
}
void down(int now, int l, int r){
    if(T[now].is){
        T[lch].is = T[rch].is = 1;
        T[now].is = 0;
        T[lch].tgx = T[lch].tgy = T[rch].tgx = T[rch].tgy = 0;
        T[lch].yy = T[lch].xx = 1.0 * (l+mid) * (mid-l+1) / 2;
        T[lch].xy = T[lch].x2 = S[mid] - S[l-1];
        T[rch].yy = T[rch].xx = 1.0 * (mid+1+r) * (r-mid) / 2;
        T[rch].xy = T[rch].x2 = S[r] - S[mid];
    }
    long double tgx = T[now].tgx, tgy = T[now].tgy;
    T[lch].x2 += (mid-l+1) * tgx * tgx + 2 * tgx * T[lch].xx;
    T[rch].x2 += (r-mid)   * tgx * tgx + 2 * tgx * T[rch].xx;
    T[lch].xy += (mid-l+1) * tgx * tgy + T[lch].xx * tgy + T[lch].yy * tgx;
    T[rch].xy += (r-mid)   * tgx * tgy + T[rch].xx * tgy + T[rch].yy * tgx;
    T[lch].xx += (mid-l+1) * tgx, T[rch].xx += (r-mid) * tgx;
    T[lch].yy += (mid-l+1) * tgy, T[rch].yy += (r-mid) * tgy;
    T[lch].tgx += tgx, T[lch].tgy += tgy;
    T[rch].tgx += tgx, T[rch].tgy += tgy;
    T[now].tgx = 0, T[now].tgy = 0;
}
long double cx, cy;
void modify(int now, int l, int r, int pos1, int pos2){
    if(pos1 == l && pos2 == r){
        T[now].tgx += cx, T[now].tgy += cy;
        T[now].x2 += (r-l+1) * cx * cx + 2 * cx * T[now].xx;
        T[now].xy += T[now].xx * cy + T[now].yy * cx + (r-l+1) * cx * cy;
        T[now].xx += (r-l+1) * cx;
        T[now].yy += (r-l+1) * cy;
        return;
    }down(now, l, r);
    if(pos2 <= mid) modify(lch, l, mid, pos1, pos2);
    else if(pos1 >= mid+1) modify(rch, mid+1, r, pos1, pos2);
    else{
        modify(lch, l, mid, pos1, mid);
        modify(rch, mid+1, r, mid+1, pos2);
    }
    update(now, l, r);
}

node que(int now, int l, int r, int pos1, int pos2){
    if(pos1 == l && pos2 == r) return T[now]; down(now, l, r);
    if(pos2 <= mid) return que(lch, l, mid, pos1, pos2);
    else if(pos1 >= mid+1) return que(rch, mid+1, r, pos1, pos2);
    else return que(lch, l, mid, pos1, mid) + que(rch, mid+1, r, mid+1, pos2);
}

void makeis(int now, int l, int r, int pos1, int pos2){
    if(pos1 == l && pos2 == r){
        T[now].is = 1;
        T[now].tgx = T[now].tgy = 0;
        T[now].yy = T[now].xx = 1.0 * (l+r) * (r-l+1) / 2;
        T[now].xy = T[now].x2 = S[r] - S[l-1];
        return;
    }down(now, l, r);
    if(pos2 <= mid) makeis(lch, l, mid, pos1, pos2);
    else if(pos1 >= mid+1) makeis(rch, mid+1, r, pos1, pos2);
    else{
        makeis(lch, l, mid, pos1, mid);
        makeis(rch, mid+1, r, mid+1, pos2);
    }
    update(now, l, r);
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) S[i] = 1.0 * i * i + S[i-1];
    for(int i = 1; i <= n; i ++) scanf("%lf", &x[i]);
    for(int i = 1; i <= n; i ++) scanf("%lf", &y[i]);
    build(1, 1, n);
    for(int i = 1; i <= m; i ++){
        int op, l, r, s, t; 
        scanf("%d", &op);
        if(op == 1){
            scanf("%d%d", &l, &r);
            node ans; 
            ans = que(1, 1, n, l, r);
            long double X = 1.0 * ans.xx / (r-l+1);
            long double Y = 1.0 * ans.yy / (r-l+1);
            long double res = ans.xy - ans.yy * X - ans.xx * Y + (r-l+1) * X * Y;
            res /= (ans.x2 - 2 * ans.xx * X + (r-l+1) * X * X);
            printf("%.10lf\n", (double)res);
        }else if(op == 2){
            scanf("%d%d%d%d", &l, &r, &s, &t);
            cx = s, cy = t;
            modify(1, 1, n, l, r);
        }else if(op == 3){
            scanf("%d%d%d%d", &l, &r, &s, &t);
            makeis(1, 1, n, l, r);
            cx = s, cy = t;
            modify(1, 1, n, l, r);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值