GRYZ20211029模拟赛解题报告

目录

期望得分:\(100+40+20 = 160pts\)
实际得分:\(100+40+0=140pts\)

搞不懂自己为什么这么菜。

T1 T2 T3
T207974 function T207975 generals T207976 toy

T1 generals

CF732D 原题。

首先你考虑一个贪心,越晚去攻击一个人,越容易打败他。因为越晚你积攒的兵力越多。

所以你可以通过判断能否在每个人最晚暴露破绽的回合去打败他来判断是否有解。

可惜题目让我们求最快多少回合干掉其他所有人。

显然上面的是最坏情况,显然一个更优的情况是在更早的回合干掉最后一个人。

然后你发现可以用 vector 和 set 配合来维护这个寻找更优的情况的过程。

然后你考虑快速判断一个局面有无解,你发现,如果把不攻击的回合的价值看做 \(1\),攻击的回合的价值看做 \(a_{d_i}\),那么如果整个序列的最小前缀和 \(\ge 0\) 就有解,否则无解。这个信息可以使用线段树维护。

然后就做完了。

时间复杂度为 \(\mathcal O(n \log n)\),因为更优的情况最多只会寻找 \(n\) 次,里面的所有操作也都是单 \(\log\) 的。因为操作有点多,可能常数较大。

然后我卡过了评测机却没有卡过 CF。

介绍一下正解:

通过上面的叙述我们已经会 \(O(n)\) 的 check 一下某个方案是否有解,并且能通过贪心得到最坏情况的方案。

那你直接二分答案就做完了。

#include<iostream>
#include<cstdio>
#include<vector>
#include<set>
#define orz cout<<"lkp AK IOI!\n"
using namespace std;
const int MAXN = 2e5 + 10;
const int INF = 1e9 + 7;
const int mod = 1e9 + 7;

struct node {
    int pos, id, rk;
    bool operator < (const node &b) const { 
        if(pos == b.pos) {
            if(id == b.id) return rk > b.rk;
            return id < b.id;
        }
        return pos > b.pos; 
    }
};

int n, m, ans = 0;
int d[MAXN], a[MAXN], b[MAXN];
int lst[MAXN];
vector<int> pos[MAXN]; 
set<node> S;

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}
namespace Seg {
    #define lson i << 1
    #define rson i << 1 | 1
    int Min[MAXN << 2], sum[MAXN << 2];
    void Push_up(int i) {
        sum[i] = sum[lson] + sum[rson];
        Min[i] = min(Min[lson], sum[lson] + Min[rson]);
    }
    void Build(int i, int l, int r) {
        if(l == r) {
            Min[i] = sum[i] = b[l];
            return ;
        }
        int mid = (l + r) >> 1;
        Build(lson, l, mid), Build(rson, mid + 1, r);
        Push_up(i);
    }
    void Modify(int i, int l, int r, int pos, int val) {
        if(l == r) {
            Min[i] = sum[i] = val;
            return ;
        }
        int mid = (l + r) >> 1;
        if(mid >= pos) Modify(lson, l, mid, pos, val);
        else Modify(rson, mid + 1, r, pos, val);
        Push_up(i);
    }
}

int main() {
//	freopen("generals.in","r",stdin);
//	freopen("generals.out","w",stdout);
    n = read(), m = read();
    for(int i = 1; i <= n; ++i) {
        d[i] = read();
        if(!d[i]) continue;
        pos[d[i]].push_back(i);
        lst[d[i]] = i;
    }
    for(int i = 1; i <= m; ++i) a[i] = read();
    for(int i = 1; i <= n; ++i) {
        if(lst[d[i]] == i) {
            b[i] = - a[d[i]];
            int M = pos[d[i]].size();
            S.insert((node){i, d[i], M - 1});
        } else {
            b[i] = 1;
        }
    }
//    for(int i = 1; i <= n; ++i) cout<<b[i]<<" "; puts("\n");
    Seg::Build(1, 1, n);
    if(Seg::Min[1] >= 0) {
        ans = S.begin()->pos;
    } else {
        puts("-1");
        return 0;
    }
    set<node>::iterator it;
    while(true) {
        it = S.begin();
        node x = *it;
        S.erase(it);
        if(x.rk == 0) {
            ans = x.pos;
            break;
        } else {
            Seg::Modify(1, 1, n, x.pos, 1);
            x.rk --;
            x.pos = pos[x.id][x.rk];
            Seg::Modify(1, 1, n, x.pos, - a[x.id]);
            int res = Seg::Min[1];
            if(res < 0) break;
            else {
                S.insert(x);
                ans = S.begin()->pos;
            }
        }
    }
    printf("%d\n", ans);
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}
/*
10 3
0 0 1 2 3 0 2 0 1 2
1 1 4
*/

T2 function

前 40pts,线筛预处理一下,暴力求 \(f(x)\)。

正解:

假设一开始有 \(2\) 这个质因子。你考虑除 \(2\) 以外的质数 \(p_i\),首先都是奇数,那取一次 \(\varphi (p_i)\) 会变成 \(p_i - 1\),可以发现这个 \(p_i - 1\) 一定可以拆成 $2 \times $ 其他某些质因子。那 \(2\) 的次数一定会 \(+1\),而每次取 \(\varphi\) 只会使 \(2\) 的次数 \(-1\),因此当 \(2\) 出现后,当存在其他质数时 \(2\) 的次数一定不会变少,换句话说,\(2\) 出现后便会一直存在。所以答案就是整个过程中产生 \(2\) 这个因子的个数。

如果一开始没有 \(2\),那第二次一定会出现 \(2\),只需要比上面的答案多做一次操作。

如果 \(\sqrt n\) 分解质因数复杂度为 \(\mathcal O(n \sqrt n)\)。

如果筛出每个数的最小质因数可以做到 \(\mathcal O(n \log n)\)。

如果预处理每个数不断取 \(\varphi\) 能产生多少个 \(2\) 可以做到 \(\mathcal O(n)\)。

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e6+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

int T, n;
int p[MAXN], q[MAXN];
int cnt[MAXN];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int prim[MAXN], vis[MAXN], Cnt = 0;
void Init() {
    int M = 1000000;
    for(int i = 2; i <= M; ++i) {
        if(!vis[i]) prim[++Cnt] = i, vis[i] = i;
        for(int j = 1; j <= Cnt && i * prim[j] <= M; ++j) {
            vis[i * prim[j]] = prim[j];
            if(i % prim[j] == 0) break;
        }
    }
}

void Work(int x, int k) {
    x--;
    while(x != 1) {
        cnt[vis[x]] += k;
        x /= vis[x];
    }
}

signed main()
{
    Init();
	T = read();
	while(T--) {
	    memset(cnt, false, sizeof cnt);
	    n = read();
	    int M = 0; bool flag = true;
	    for(int i = 1; i <= n; ++i) {
	        p[i] = read(), q[i] = read();
	        cnt[p[i]] += q[i];
	        if(p[i] == 2) flag = false;
	        M = max(p[i], M);
        }
        for(int i = M; i >= 3; --i) {
            if(!cnt[i]) continue;
            Work(i, cnt[i]);
        }
        printf("%lld\n", cnt[2] + flag);
    }
    return 0;
}

T3 toy

CF618E 原题

把每条线段看做一个复数,然后转化成复数的三角形式。

伸长可以转化成扩大 \(k\) 倍,其中 \(k\) 是伸长后的长度和原长的比值。

而旋转利用高中数学的相关知识就可以解决。

\[\mid r_1 \mid (\cos \alpha + i \sin \alpha) \times \mid r_2 \mid (\cos \beta + i \sin \beta) = \mid r_1 \mid \mid r_2 \mid (\cos (\alpha + \beta) + i \sin (\alpha + \beta)) \]

重载一下复数就是个线段树板子了。

另外提一句,C++ 里面用的是弧度制,角度制转弧度制的公式为 \(\frac{n \pi}{180}\),\(\pi\) 需要自己定义,math.h 存有 \(\pi\) 的值,大约 20 位的样子,应该够用了。

再提一句 std 用的是矩阵,被这个算法爆踩了。

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 3e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
const double pi = 3.14159265358979323846;

struct Complex { // 复数 
    double x, y;
    Complex() { x = y = 0.0; }
    Complex(double a, double b) { x = a, y = b; }
    Complex(double angle) { x = cos(angle), y = sin(angle); }
    Complex operator + (Complex b) { return Complex(this->x + b.x, this->y + b.y); }
    Complex operator * (Complex b) { return Complex(this->x * b.x - this->y * b.y, this->y * b.x + this->x * b.y); }
    double Calc() { return sqrt(x * x + y * y); }
};

int n, m;

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

namespace Seg {
    #define lson i << 1
    #define rson i << 1 | 1
    Complex sum[MAXN << 2], lazy[MAXN << 2];
    void Push_up(int i) { sum[i] = sum[lson] + sum[rson]; }
    void Build(int i, int l, int r) {
        lazy[i].x = 1.0, lazy[i].y = 0.0;
        if(l == r) {
            sum[i].x = 1.0, sum[i].y = 0.0;
            return ;
        }
        int mid = (l + r) >> 1;
        Build(lson, l, mid), Build(rson, mid + 1, r);
        Push_up(i);
    }
    void Push_down(int i) {
        lazy[lson] = lazy[lson] * lazy[i], lazy[rson] = lazy[rson] * lazy[i];
        sum[lson] = sum[lson] * lazy[i], sum[rson] = sum[rson] * lazy[i];
        lazy[i].x = 1.0, lazy[i].y = 0.0;
    }
    void Modify(int i, int l, int r, int L, int R, Complex v) {
        if(L <= l && r <= R) {
            sum[i] = sum[i] * v, lazy[i] = lazy[i] * v;
            return ;
        }
        Push_down(i);
        int mid = (l + r) >> 1;
        if(mid >= L) Modify(lson, l, mid, L, R, v);
        if(mid < R) Modify(rson, mid + 1, r, L, R, v);
        Push_up(i);
    }
    Complex Query(int i, int l, int r, int pos) {
        if(l == r) return sum[i];
        Push_down(i);
        int mid = (l + r) >> 1; Complex ans;
        if(mid >= pos) return Query(lson, l, mid, pos);
        else return Query(rson, mid + 1, r, pos);
    }
}

int main()
{
	n = read(), m = read();
	Seg::Build(1, 1, n);
	for(int i = 1, x, y; i <= m; ++i) {
	    double z;
	    scanf("%d%d%lf", &x, &y, &z);
	    if(x == 1) {
	        Complex res = Seg::Query(1, 1, n, y);
	        Seg::Modify(1, 1, n, y, y, Complex(1.0 + 1.0 * z / res.Calc(), 0));
        } else {
            Seg::Modify(1, 1, n, y, n, Complex(1.0 * pi * (-z) / 180.0));
        }
        printf("%.10lf %.10lf\n", Seg::sum[1].x, Seg::sum[1].y);
    }
    return 0;
}
上一篇:设计模式-单例模式


下一篇:spring成神之路第十二篇:lazy-init:bean延迟初始化