zoukankan      html  css  js  c++  java
  • 【解题报告】牛客暑假多校 Round 1

    比赛链接: https://ac.nowcoder.com/acm/contest/11166

    还在补题,但是好多,补不完了qwq。

    已完成:A (B) D F I J

    A - Alice and Bob

    题意简介

    两堆石子,分别是 (n)(m) 个,Alice 先取,Bob 后取。每次,可以从其中一堆中取走 (k) ( (k>0) ) 个,然后从另一堆中取走 (s imes k) ( (sgeq 0) ) 。轮到某个人时石子都没了,那个人就会输掉。现在给你 10000 组询问,(1leq n,mleq 5 imes 10^3),要你输出每组询问谁会赢。

    思路分析

    想不出正解,但是先暴力算一遍再说。记 (f(i,j)) 表示第一堆石子剩下 (i) ,第二堆石子剩下 (j) 的情况下先手方的状态,(1) 表示必胜 ,有:(f(i,j) = max{(1-f(i-k,j-ks))})

    因为发现必败态只有 2719 种,于是存下来,每次询问直接二分查找。

    上面是比赛时用的打表思路。

    经过后续的听讲,我们知道这道题可以利用每个 i 只唯一对应一个必败态来搞事,而且据出题人说可以跑得飞快,于是我就自己敲了一个。

    思路就是用 (Q_i) 存下对于每个 (i) ,与之唯一对应的必败态 (j)(当然也可能不存在,记为 (-1))。

    然后再根据这些必败态,枚举倍数来排除必胜态,找到下一个必败态。

    理论复杂度是 (O(n^2 log n)) 的,但因为减了不少枝,所以跑得飞快,500 来毫秒左右。

    但是我并没有理解出题人说的 (n = 10000) 也可以跑出来需要怎么做,因为我无法省去这个二维数组,求大佬教教我qwq。

    解题代码

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 5005;
    int n, m, Q[N]; bool vis[N][N];
    int main() {
    	memset(Q, -1, sizeof(Q));
    	Q[0] = 0;
    	for(int i=0; i<=5000; i++) {
    		if(Q[i] != -1) {
    			for(int j=Q[i]+1; j<=5000; j++) {
    				for(int k=i; k<=5000; k+=j-Q[i]) vis[k][j] = 1;
    			}
    			continue;
    		}
    		for(int j=i-1; j>=0; j--) {
    			if(Q[j] == -1) continue;
    			for(int k=Q[j]; k<=5000; k+=i-j) vis[i][k] = 1;
    		}
    		for(int j=i+1; j<=5000; j++) {
    			if(!vis[i][j]) {
    				Q[i] = j, Q[j] = i;
    				break;
    			}
    		}
    		if(Q[i] != -1) {
    			for(int j=Q[i]+1; j<=5000; j++) {
    				for(int k=i+j-Q[i]; k<=5000; k+=j-Q[i]) vis[k][j] = 1;
    			}
    		}
    	}
    	int t; scanf("%d", &t);
    	while(t--) {
    		scanf("%d%d", &n, &m);
    		if(Q[n] != -1 && Q[n] == m) puts("Bob");
    		else puts("Alice");
    	}
    	return 0;
    }
    

    B - Ball Dropping

    题意简介

    如上图,给你 (a,b,r,h) ,问你球是否会从漏斗里掉出去。如果不会,求图上红边的长度。

    数据保证 (2r eq b) , (2r<a) , (2r<h)

    思路分析

    队友切的。几何题一生之敌qwq。

    TODO

    通过代码见:提交记录

    C - Cut the Tree

    题意简介

    TODO

    思路分析

    TODO

    D - Determine the Photo Position

    题意简介

    给你一个只含 0 和 1 的 (n imes n) 的矩阵,表示相片上有的地方站了学生有的地方没站。现在告诉你有 (m) 个老师站成一排,要求你把这一排老师完整地、不能拆分或旋转地,插入到相片地空白处。问你有多少种插入方式。

    思路分析

    签到。求出所有的连续的 0 段的 0 的数目 c,每个连续 0 段对答案的贡献是 (max{(c-m+1, 0)})

    解题代码

    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int N=2005;
    int n,m; char s[N][N],s2[N];
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf(" %s",s[i]+1);
        scanf(" %s",s2+1);
        int ans=0;
        for(int i=1;i<=n;i++){
            int c0=0;
            for(int j=1;j<=n+1;j++){
                if(s[i][j]!=s[i][j-1]){
                    if(s[i][j]!='0'){
                        ans+=max(0,c0-m+1);
                        c0=0;
                    }else{
                        c0=1;
                    }
                }else if(s[i][j]=='0') c0++;
            }
        }
        printf("%d",ans);
        return 0;
    }
    

    E - Escape along Water Pipe

    题意简介

    TODO

    思路分析

    TODO

    F - Find 3-friendly Integers

    题意简介

    一个整数被称为 3友好整数 ,当且仅当可以从它的十进制表示中找到一个连续子串,这个连续子串的表示的数字是 3 的倍数。

    给你 10000 组询问,问你 (L)(R) 里有多少个整数满足上述要求。( (1leq Lleq R leq 10^{18}) )

    思路分析

    我们知道,一个数是三的倍数,那么它的各位数之和也是三的倍数。

    想要方便直观地各位数之和,那么我们可以给每一位数都模个 3 。

    我们知道,想要一个数想要不满足题意,它的各个数字模 3 后首先不能是 0 。

    所以,我们试着用数字 1 和 2 来构造不满足题意的数,不难发现,根本无法构造出 4 位数以上的数字。

    所以,对于位数少的,我们直接预处理出那些数字满足题意,对于位数大的部分,它一定是满足题意的,直接 (R-L+1) 就完事了。

    解题代码

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int N=1e6;
    int vis[N];
    inline void calc(int x){
        static int dig[1000];
        int cn=0;
        int ux=x;
        while(x){
            dig[++cn]=x%10;
            x/=10;
        }
        for(int i=1;i<=cn;i++) dig[i]+=dig[i-1];
        for(int i=1;i<=cn;i++) for(int j=i;j<=cn;j++){
            if((dig[j]-dig[i-1])%3==0){
                vis[ux] = 1; return;
            }
        }
        return;
    }
    int main(){
        int t; scanf("%d",&t);
        vis[0]=1;
        for(int i=1;i<=N;i++) {
            calc(i);
            vis[i]+=vis[i-1];
        }
        while(t--){
            ll L,R; scanf("%lld%lld",&L,&R);
            ll ans=0;
            if(R<=N) ans=vis[R]-vis[L-1];
            else if(L<=N) ans=R-N+vis[N]-vis[L-1];
            else ans=R-L+1;
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    G - Game of Swapping Numbers

    题意简介

    TODO

    思路分析

    TODO

    H - Hash Function

    题意简介

    给你一个大小为 (n) 的没有重复数字的集合 ({a_i}),问你使集合内所有数对 (x) 取模的答案两两不同的最小 (x)(0leq a_ileq 500000)(1leq n leq 500000)

    思路分析

    比赛时开桶暴力瞎搞的。数据加强了之后烂了。不会多项式,下一个。

    TODO

    I - Increasing Subsequence

    题意简介

    Alice 和 Bob 轮流对一个排列进行取数。每次取都要比上一个被取的数字更大。同时,对于同一个人,他取数时除了要满足比上一个数大外,还要求新取的数要在自己上一次取的数的右边。如果这一轮里一个人可以取的数不止一个,他会随机等概率拿一个出来。求游戏进行的轮次的期望值。

    思路分析

    比赛时有一个 O(n^3) 的思路,但是没时间了,细节也没搞清楚,于是没搞出来,赛后重新复盘了一下。

    我们记 (p(i, j)) 为 Alice 选了 (i) 次,Bob 选了 (j) 次的概率。

    显然的,(A_i > A_j) 时,当前这个状态最后一次取的人是 Alice(因为每次取数要比上一次取数更大),所以这个状态必然是由 (p(k, j)) 转移过来的。且根据题意,我们可以知道,这个状态必须满足 (A_k < A_j),和 (k < i)

    而从 ((k ,j)) 转移到 ((i, j)) ,意味着 Alice 在 (k) 往后的位置中所有能选的位置里,恰好挑中了 (i)

    于是,我们有转移:(p(i, j) = sum p(k, j) imes frac{1}{c}),其中 (c) 表示 (k) 往后的位置中能选的位置的数量,且 (k) 必须满足上面分析的条件(搞进公式里太复杂了,自已意会一下好了qwq)。

    同理,对于 (A_j > A_i),我们有转移:(p(i, j) = sum p(i, k) imes frac{1}{c})

    有了这个东西之后,轮次期望的转移就变得非常明显了。

    我们记 (f(i, j)) 为 Alice 选了 (i) 次,Bob 选了 (j) 次的轮次期望。

    于是,对于 (A_i > A_j) 有:(f(i, j) = sum f(k, j) imes frac{1}{c} + 1 imes p(k, j) imes frac{1}{c})

    小于的情况类似。

    接下来考虑如何实现。

    首先,题目 (n leq 5000) ,因此我们只能是 (O(n^2)) ,甚至不能带 (log n) 。发现需要求逆元的 (c) 小于 (n),所以我们先预处理出 (1)(n) 的所有逆元。

    对于 (c) 的快速求解,我们可以开个二维数组,用 (cnt(i, j)) ,表示从下标 (i) 开始的后缀里,值小于等于 (j) 的数量(相当于对每个后缀开了一个桶)。

    接下来,仔细观察转移的式子,我们可以发现,大于的状态必然从小于的状态转移过来,而且转移过程与 (i)(j))无关,所以我们可以维护类似前缀和的东西。

    具体实现见代码。

    解题代码

    代码中的注释部分为 (O(n^3)) 的写法。

    #include <cstdio>
    typedef long long ll;
    const ll mod = 998244353, N = 5005;
    int n; ll A[N], P[N], f[N][N], cnt[N][N], p[N][N], sp1[N], sf1[N], sp2[N][N], sf2[N][N], inv[N];
    inline char getc() {
        static char buf[1<<14], *p1 = buf, *p2 = buf;
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<14, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline ll scan() {
        ll x = 0; char ch = 0;
        while(ch < 48) ch = getc();
        while(ch >= 48) x = x * 10 + ch - 48, ch = getc();
        return x;
    }
    inline ll _pow(ll x, ll p) {
        ll ans = 1;
        while(p) {
            if(p&1) ans = ans * x %mod;
            x = x * x % mod; p >>= 1;
        }
        return ans;
    }
    inline ll _inv(ll x) {
        return _pow(x, mod - 2);
    }
    inline ll add(ll a, ll b) {
        return a + b >= mod ? a + b - mod : a + b;
    }
    int main() {
        n = scan();
        for(int i=1; i<=n; i++) {
            A[i] = scan();
            P[A[i]] = i;
            inv[i] = _inv(i);
        }
        for(int i=1; i<=n; i++) {
            for(int j=i; j<=n; j++) {
                cnt[i][A[j]]++;
            }
            for(int j=1; j<=n; j++) {
                cnt[i][j] += cnt[i][j-1];
            }
        }
        p[0][0] = 1;
        for(int i=1; i<=n; i++) {
            p[i][0] = f[i][0] = inv[n];
            int _c = cnt[1][n] - cnt[1][A[i]]; 
            sp2[i][0] = p[i][0] * inv[_c] % mod;
            sf2[i][0] = (f[i][0] + 1 * p[i][0]) * inv[_c] % mod;
            for(int j=1; j<=n; j++) {
                // 计算 f[i][j] 和 p[i][j]
                if(A[i] > A[j]) {
                    /*for(int k=1; k<i; k++) {
                        if(A[k] < A[j]) {
    						int c = cnt[k+1][n] - cnt[k+1][A[j]];
                            p[i][j] += p[k][j] * _inv(c) % mod;
    						p[i][j] %= mod;
    						f[i][j] += (f[k][j] + 1 * p[k][j]) * _inv(c) % mod;
                            f[i][j] %= mod;
                        }
                    }*/
                    p[i][j] = add(p[i][j], sp1[j]);
                    f[i][j] = add(f[i][j], sf1[j]);
                    // 维护 sp2 和 sf2
                    int c = cnt[j+1][n] - cnt[j+1][A[i]];
                    sp2[i][j] = (sp2[i][j] + p[i][j] * inv[c]) % mod;
                    sf2[i][j] = (sf2[i][j] + (f[i][j] + p[i][j]) * inv[c]) % mod;
                }
    			if(A[i] < A[j]) {
                    /*for(int k=0; k<j; k++) {
                        if(A[k] < A[i]) {
    						int c = cnt[k+1][n] - cnt[k+1][A[i]];
    						p[i][j] += p[i][k] * _inv(c) % mod;
    						p[i][j] %= mod;
    						f[i][j] += (f[i][k] + 1 * p[i][k]) * _inv(c) % mod;
                            f[i][j] %= mod;
                        }
                    }*/
                    p[i][j] = add(p[i][j], sp2[i][j-1]);
                    f[i][j] = add(f[i][j], sf2[i][j-1]);
                }
                sp2[i][j] = add(sp2[i][j], sp2[i][j-1]);
                sf2[i][j] = add(sf2[i][j], sf2[i][j-1]);
            }
            // 维护前缀和
            for(int j=0; j<=n; j++) {
                if(A[i] < A[j]) {
                    int c = cnt[i+1][n] - cnt[i+1][A[j]];
                    sp1[j] = (sp1[j] + p[i][j] * inv[c]) % mod;
                    sf1[j] = (sf1[j] + (f[i][j] + p[i][j]) * inv[c]) % mod;
                }
            }
        }
        ll res = 0;
        for(int i=1; i<=n; i++) for(int j=0; j<=n; j++) {
            if(A[i] > A[j]) {
                int c = cnt[j+1][n] - cnt[j+1][A[i]];
                if(c == 0) res = add(res, f[i][j]);
            }
            if(A[j] > A[i]) {
                int c = cnt[i+1][n] - cnt[i+1][A[j]];
                if(c == 0) res = add(res, f[i][j]);
            }
        }
        printf("%lld", res);
        return 0;
    }
    
    

    J - Journey among Railway Stations

    题意简介

    (n) 个车站,每个车站有一个开启时间和一个关闭时间。如果提早到了,需要等到开启时间才能走,如果晚到了,就再也过不去了。

    相邻车站有一条从左到右的单向道路,表示需要走多少时间才能从一个车站到另一个车站。

    现在有两种操作,一种询问:

    1. 修改车站的起始时间。

    2. 修改车站间路的长度。

    3. 询问能否从第 (l) 个车站移动到第 (r) 个车站并通过。

    思路分析

    看一眼数据范围:(n leq 10^6) 。还有修改和查询。是数据结构哒!

    然后我就溜了。

    补题的时候听了下讲解。这道题是个很巧妙的线段树题。

    我们可以观察到,不管是多长的区间,我们画出进入区间的时间与离开区间的最早时间的函数,你总能发现这样的三部分:

    1. 早来了,在移动过程中需要经过等待,最终出区间的时间一样。

    2. 中间不需要任何等待,出区间的时间随入区间的时间单调递增,且斜率为 1。

    3. 来晚了,过不去了。

    不难发现,我们只要维护三个值,就可以推出完整的函数图像:

    1. wait: 在这个时间之前来,一定需要等待

    2. late: 在这个时间之后来,一定过不去

    3. time: 从 wait 这个时间前来,会在什么时候离开(也就是可能的最早离开时间)。

    具体的维护和初始化,画画图,仔细想想、分类讨论一下可以搞出来(语文能力差,表达不出来)。可以参考代码的 merge 函数,可能写得有点丑qwq。

    因为都是单点修改,所以每次改完后递归到底,往上一路 maintain 就完事了。

    解题代码

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int N = 1e6 + 5;
    #define ls (u<<1)
    #define rs ((u<<1)+1)
    struct Node {
    	ll wait, late, time;
    	Node(): wait(0), late(0), time(0) {}
    }Tr[N<<2];
    int s[N], e[N], cost[N], n, q;
    inline void debug(Node x) {
    	printf("Wait: %lld, late: %lld, time: %lld
    ", x.wait, x.late, x.time);
    }
    inline void output(int u, int l, int r) {
    	if(l > r) return;
    	printf("For [%d, %d]: ", l, r); 
    	debug(Tr[u]);
    	if(l == r) return;
    	int mid = (l + r) >> 1;
    	output(ls, l, mid); 
    	output(rs, mid + 1, r);
    }
    inline Node merge(Node A, Node B, ll w) {
    	Node C = Node();
    	if(A.time == -1 || B.time == -1) return C.time = -1, C;
    	ll ti = A.time + w;
    	if(ti > B.late) return C.time = -1, C;
    	if(ti >= B.wait) C.wait = A.wait;
    	else {
    		C.wait = A.wait + (B.wait - ti);
    		ti = B.wait;
    	}
    	C.time = B.time + (ti - B.wait);
    	C.late = min(A.late, C.wait + (B.late - ti));
    	return C;
    }
    inline void maintain(int u, int l, int r) {
    	int mid = (l + r) >> 1;
    	Tr[u] = merge(Tr[ls], Tr[rs], cost[mid]);
    }
    void build(int u, int l, int r) {
    	if(l == r) {
    		Tr[u].wait = s[l]; 
    		Tr[u].late = e[l];
    		Tr[u].time = s[l];
    		return;
    	}
    	int mid = (l + r) >> 1;
    	build(ls, l, mid), build(rs, mid + 1, r);
    	return maintain(u, l, r);
    }
    Node query(int u, int l, int r, int ql, int qr) {
    	if(l > qr || r < ql) return Node();
    	if(ql <= l && r <= qr) return Tr[u];
    	int mid = (l + r) >> 1;
    	if(l > qr || mid < ql) return query(rs, mid + 1, r, ql, qr);
    	if(mid + 1 > qr || r < ql) return query(ls, l, mid, ql, qr);
    	return merge(query(ls, l, mid, ql, qr), query(rs, mid + 1, r, ql, qr), cost[mid]);
    }
    void update(int u, int l, int r, int x) {
    	if(l > x || r < x) return;
    	if(l == r) {
    		Tr[u].wait = s[l];
    		Tr[u].late = e[l];
    		Tr[u].time = s[l];
    		return;
    	}
    	int mid = (l + r) >> 1;
    	update(ls, l, mid, x); update(rs, mid + 1, r, x);
    	return maintain(u, l, r);
    }
    int main() {
    	int T; scanf("%d", &T);
    	while(T--) {
    		scanf("%d", &n);
    		for(int i=1; i<=n; i++) scanf("%d", s+i);
    		for(int i=1; i<=n; i++) scanf("%d", e+i);
    		for(int i=1; i<n; i++) scanf("%d", cost+i); 
    		build(1, 1, n);
    		// puts("=======================");
    		// output(1, 1, n);
    		// puts("=======================");
    		scanf("%d", &q);
    		while(q--) {
    			int opt; scanf("%d", &opt);
    			if(opt == 0) {
    				int l, r; scanf("%d%d", &l, &r);
    				Node ans = query(1, 1, n, l, r);
    				puts(ans.time == -1 ? "No" : "Yes");
    			}
    			if(opt == 1) {
    				int i, w; scanf("%d%d", &i, &w);
    				cost[i] = w;
    				update(1, 1, n, i);
    			}
    			if(opt == 2) {
    				int i, _s, _e;
    				scanf("%d%d%d", &i, &_s, &_e);
    				s[i] = _s, e[i] = _e;
    				update(1, 1, n, i);
    			}
    			/* if(opt) {
    			 	puts("=======================");
    				output(1, 1, n);
    				puts("=======================");
    			} */
    		}
    	}
    	return 0;
    }
    

    K - Knowledge Test about Match

    题意简介

    TODO

    思路分析

    TODO

  • 相关阅读:
    openjudge-NOI 2.6-1996 登山
    openjudge-NOI 2.6-1944 吃糖果
    openjudge-NOI 2.6-1808 公共子序列
    openjudge-NOI 2.6-1775 采药
    openjudge-NOI 2.6-1768 最大子矩阵
    openjudge-NOI 2.6-1759 最长上升子序列
    tyvj P1050 最长公共子序列
    动态规划-最长公共子序列
    动态规划-最长上升子序列
    HDU 1811 Rank of Tetris
  • 原文地址:https://www.cnblogs.com/Qing-LKY/p/nk11166.html
Copyright © 2011-2022 走看看