1. 程式人生 > 其它 >GRYZ20211029模擬賽解題報告

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;
}