• Codeforces Round #543 (Div. 1, based on Technocup 2019 Final Round) 题解


    题面戳这里


    A. Diana and Liana

    首先如果s>ks>k一定无解,特判一下。那么我们考虑找恰好满足满足题目中的要求的区间[l,r][l,r],那么需要要删去的数一定是(l1)%k+max((rl+1)k,0)(l-1)\%k+max((r-l+1)-k,0)。前面的表示要把[1,l1][1,l-1]区间删成kk的倍数,后面的表示要把这个区间删到kk长度以内。判断这个值是否不大于能够删的最大值mnkm-ncdot k,然后更新答案。

    不能枚举,但是当ll增大,恰好满足条件的区间的rr一定也会增大。那么就枚举左端点,右端点向右移动就行了。

    CODE

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 500005;
    int m, n, k, s, a[MAXN], b[MAXN], mx;
    int cnt[MAXN], need[MAXN], now, all, sum;
    int main () {
    	scanf("%d%d%d%d", &m, &k, &n, &s); mx = m-n*k;
    	if(s > k) return puts("-1"), 0;
    	for(int i = 1; i <= m; ++i) scanf("%d", &a[i]);
    	for(int i = 1; i <= s; ++i) {
    		scanf("%d", &b[i]);
    		if(++need[b[i]] == 1) ++all;
    	}
    	int r = 0;
    	for(int i = 1; i <= m && r <= m; ++i) {
    		int tmp = (i-1)%k;
    		while(now < all && r < m) {
    			++cnt[a[++r]];
    			if(cnt[a[r]] == need[a[r]]) ++now;
    		}
    		if(now == all && tmp + max(r-i+1-k, 0) <= mx) {
    			printf("%d
    ", tmp + max(r-i+1-k, 0));
    			for(int j = 1; j <= tmp; ++j)
    				printf("%d ", j);
    			int left = max(r-i+1-k, 0);
    			for(int j = i; j <= r && left; ++j)
    				if(cnt[a[j]] > need[a[j]]) {
    					--left; --cnt[a[j]];
    					printf("%d ", j);
    				}
    			return 0;
    		}
    		if(--cnt[a[i]] == need[a[i]]-1) --now;
    	}
    	puts("-1");
    }
    

    B. Once in a casino

    直接从左到右考虑,假设前面的位置都已经满足了,那么这位上的数需要加几/减几就直接加/减上去,但是这样有可能让下一位加到9以上或者减到0以下,暂时先不管。如果到最后an≠bna_n= ot b_n就无解,反之一定有解(显然)。然后因为只要求输出前10510^5次,模拟操作即可:直接从左到右贪心执行操作,遇到越界的情况就递归就行了。

    代码挺丑的,能过就行。

    CODE

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 100005;
    int n, A[MAXN], cur;
    long long ans;
    char a[MAXN], b[MAXN];
    inline int ff(int x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }
    inline bool chkout(int x) { return !(x >= 0 && x <= 9); }
    void solve(int i, int val) { //solve(i,val)表示把i加上val
        if(cur == 100000) return;
        //if(i > n) return;
        int k = ff(val);
        if(chkout(A[i+1]+val)) {
            if(A[i+1]+val > 9) solve(i+1, 9-(A[i+1]+val));
            else solve(i+1, 0-(A[i+1]+val));
        }
        if(cur == 100000) return;
        while(val) {
            ++cur;
            printf("%d %d
    ", i, k);
            val -= k, A[i] += k, A[i+1] += k;
            if(cur == 100000) return;
        }
    }
    int main () {
    	scanf("%d", &n);
    	scanf("%s", a+1);
    	scanf("%s", b+1);
    	for(int i = 1; i <= n; ++i)
            A[i] = a[i]-'0';
        for(int i = 1; i <= n; ++i) {
            A[i+1] += b[i]-'0'-A[i];
            ans += abs(b[i]-'0'-A[i]);
            A[i] = b[i]-'0';
        }
        if(A[n+1]) puts("-1");
        else {
            printf("%I64d
    ", ans);
            for(int i = 1; i <= n; ++i)
                A[i] = a[i]-'0';
            for(int i = 1; i <= n && cur < 100000; ++i)
                solve(i, b[i]-'0'-A[i]);
        }
    }
    

    C. Compress String

    直接预处理出来每两个位置的后缀的最长公共前缀记作mx[i][j]mx[i][j],因为是50005000O(n2)dpO(n^2)dp就行了(也可以SASA)。

    定义f[i]=maxj&lt;imx[i][j]f[i]=max_{j&lt;i} mx[i][j]dp[i]dp[i]表示到前ii个最小费用,O(n2)O(n^2)转移很显然:
    dp[i]=min(dp[i1]+a , minf[j](ij+1), jidp[j1] + b)dp[i]=min(dp[i-1]+a , min_{f[j]ge (i-j+1), jle i} dp[j-1] + b)

    也有trietrie树/SAMSAM的算法

    CODE

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 5005;
    int n, a, b, dp[MAXN], mx[MAXN][MAXN], f[MAXN];
    char s[MAXN];
    int main () {
    	scanf("%d%d%d", &n, &a, &b);
    	scanf("%s", s+1);
    	for(int i = n; i; --i) {
            for(int j = n; j; --j) {
                if(s[i] == s[j]) mx[i][j] = mx[i+1][j+1] + 1;
                f[i] = max(f[i], min(mx[i][j], i-j));
            }
    	}
        dp[1] = a;
        for(int i = 2; i <= n; ++i) {
            dp[i] = dp[i-1] + a;
            for(int j = 2; j <= i; ++j) {
                if(f[j] >= i-j+1)
                    dp[i] = min(dp[i], dp[j-1] + b);
            }
        }
        printf("%d
    ", dp[n]);
    }
    

    D. Power Tree

    树形dpdp

    定义dp[u][0]dp[u][0]表示uu子树内节点全部统一成一个值且此值可控
    定义dp[u][1]dp[u][1]表示uu子树内节点全部统一成一个值且此值不可控

    转移如下:
    dp[u][1]=min(vSdp[v][0]+dp[w][1](w̸S))dp[u][1]=minleft(sum_{vin S} dp[v][0]+dp[w][1](win ot S) ight)
    dp[u][0]=min(dp[v][0] ,dp[u][1]+c[u])dp[u][0]=minleft(sum dp[v][0] ,dp[u][1]+c[u] ight)

    答案就是dp[1][0]dp[1][0]

    要求哪些可能被占领就再dfs一遍就行了。我的写法记忆化了一下。

    总时间复杂度O(n)O(n)

    CODE

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 200005;
    int n, c[MAXN];
    vector<int>G[MAXN];
    long long f[MAXN][2];
    void dfs(int u, int ff) {
    	if(G[u].size() == 1 && u != 1) { //注意判根
    		f[u][0] = c[u];
    		f[u][1] = 0;
    		return;
    	}
    	long long tmp = 1ll<<60, sum = 0;
    	for(auto v : G[u])
    		if(v != ff) {
    			dfs(v, u);
    			sum += f[v][0];
    			tmp = min(tmp, f[v][1]-f[v][0]);
    		}
    	//printf("%I64d
    ", sum);
    	f[u][1] = tmp + sum;
    	f[u][0] = min(sum, f[u][1] + c[u]);
    }
    bool can[MAXN], vis[MAXN][2];
    void dfs2(int u, int tp, int ff) {
    	if(vis[u][tp]) return; //记忆化
    	vis[u][tp] = 1;
    	if(G[u].size() == 1 && u != 1) { //注意判根
    		if(tp == 0)
    			can[u] = 1;
    		return;
    	}
    	long long tmp = 1ll<<60, sum = 0;
    	for(auto v : G[u])
    		if(v != ff) {
    			sum += f[v][0];
    			tmp = min(tmp, f[v][1]-f[v][0]);
    		}
    	//f[u][1] = tmp + sum;
    	//f[u][0] = min(sum, f[u][1] + c[u]);
    	if(tp == 0) {
    		if(f[u][0] == f[u][1] + c[u]) {
    			can[u] = 1;
    			int cnt = 0; //如果 cnt>1 两个儿子都可能被选中成为f[w][1], 那么大家都可能被选作f[v][0]
    			for(auto v : G[u])
    				if(v != ff) if(f[v][1]-f[v][0] == tmp) dfs2(v, 1, u), ++cnt;
    			for(auto v : G[u])
    				if(v != ff && (cnt > 1 || f[v][1]-f[v][0] != tmp))
    					dfs2(v, 0, u);
    		}
    		if(f[u][0] == sum) {
    			for(auto v : G[u])
    				if(v != ff) dfs2(v, 0, u);
    		}
    	}
    	else {
    		int cnt = 0;
    		for(auto v : G[u])
    			if(v != ff) if(f[v][1]-f[v][0] == tmp) dfs2(v, 1, u), ++cnt;
    		for(auto v : G[u])
    			if(v != ff && (cnt > 1 || f[v][1]-f[v][0] != tmp))
    				dfs2(v, 0, u);
    	}
    }
    int main () {
    	scanf("%d", &n);
    	for(int i = 1; i <= n; ++i)
    		scanf("%d", &c[i]);
    	for(int i = 1, x, y; i < n; ++i)
    		scanf("%d%d", &x, &y),
    		G[x].push_back(y),
    		G[y].push_back(x);
    	dfs(1, -1);
    	printf("%I64d ", f[1][0]);
    	dfs2(1, 0, -1);
    	int ans = 0;
    	for(int i = 1; i <= n; ++i)
    		if(can[i]) ++ans;
    	printf("%d
    ", ans);
    	for(int i = 1; i <= n; ++i)
    		if(can[i]) printf("%d ", i);
    }
    

    E. The very same Munchhausen

    考虑是否能拆位处理:

    低位到高位枚举,设f[x][i][j][0/1]f[x][i][j][0/1]表示枚举到第xx位,前x1x−1位构成的数×a×a后累积到当前位的需要加上的值为ii,前x1x−1位构成的数×a×a的数位之和×a×a与前x1x−1个数位之和的差为jj,是否所有数位均为00时的状态是否合法。
    f[x][i][j][0/1]=1f[x][i][j][0/1]=1表示当前状态合法,f[x][i][j][0/1]=0f[x][i][j][0/1]=0表示不合法。

    观察转移可以发现:

    xx这一维可以省略
    求解f[i][j][0/1]f[i][j][0/1]可以转成090−9枚举当前位向外bfsbfs
    答案状态即f[0][0][1]f[0][0][1],为输出答案,记录dig[i][j][0/1]dig[i][j][0/1]表示最高位的值(090−9),pre[i][j][0/1]pre[i][j][0/1]三元组表示状态f[i][j][0/1]f[i][j][0/1]的前驱。

    计算得到i1000i≤1000

    1000j1000−1000≤j≤1000(并不会证明),从而保证了复杂度。

    CODE

    #include <bits/stdc++.h>
    using namespace std;
    const int K = 1000;
    struct node { int x, y, z; }pre[1005][2005][2];
    queue<node>q;
    int a, dig[1005][2005][2];
    bool vis[1005][2005][2];
    inline string solve() {
    	vis[0][K][0] = 1;
    	dig[0][K][0] = -1;
    	q.push({0, K, 0});
    	while(!q.empty()) {
    		int i = q.front().x, j = q.front().y, tp = q.front().z;
    		if(i == 0 && j == K && tp == 1) {
    			string re = ""; node tmp;
    			while(~dig[i][j][tp]) {
    				if(!re.empty() || dig[i][j][tp])
    					re += dig[i][j][tp]+'0';
    				tmp = pre[i][j][tp];
    				i = tmp.x, j = tmp.y, tp = tmp.z;
    			}
    			return re;
    		}
    		for(int k = 0; k < 10; ++k) {
    			int x = (i + k * a) / 10;
    			int y = (i + k * a) % 10 * a + j - k;
    			int z = (tp || k);
    			if(y >= 0 && y <= 2000 && !vis[x][y][z])
    				vis[x][y][z] = 1, dig[x][y][z] = k, pre[x][y][z] = q.front(), q.push({x, y, z});
    		}
    		q.pop();
    	}
    	return "-1";
    } 
    
    int main () {
    	scanf("%d", &a);
    	cout<<solve()<<endl;
    }
    
    

    F. Secret Letters

    实在无力写了,自己看这里

    CODE

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 100005;
    int n, c, d, t[MAXN], tp[MAXN];
    int main () {
    	scanf("%d%d%d", &n, &c, &d);
    	char ss[2];
    	for(int i = 0; i < n; ++i) {
    		scanf("%d%s", &t[i], ss);
    		tp[i] = ss[0] == 'P' ? 0 : 1;
    	}
    	scanf("%d", &t[n]);
    	int last; tp[n] = -1;
    	long long s = 0, ans = 1ll * n * d;
    	for(int i = n; i; --i) {
    		if(tp[i-1] != tp[i]) last = t[i]; //i=n 时一定会执行这一条, 把last设为t[n]
    		else s += min(1ll*d, 1ll*(last-t[i])*c);
    		ans = min(ans, s + 1ll * (i-1) * d + 1ll*(t[n]-t[i-1]) * c);
    		//s指后面i...n的代价    (i-1)d 指 0...i-2 直接送的代价  后面这一坨是i-1贯穿整过程的代价
    	}
    	printf("%I64d
    ", ans);
    }
    
  • 相关阅读:
    [转]Entity Framework 和NHibernate的区别
    NHibernate One Session Per Request简单实现
    memcache和memcached之间的区别
    Memcached 内存分配机制介绍
    linux,apache,php,mysql常用的查看版本信息的方法
    Linux查看文件夹大小
    Linux查看系统配置常用命令
    php 文件缓存(转)
    memcache在xampp下的安装
    通过改进代码将for循环遍历数组提高效率
  • 原文地址:https://www.cnblogs.com/Orz-IE/p/12039274.html
Copyright © 2020-2023  润新知