zoukankan      html  css  js  c++  java
  • 1130考试总结

    1130考试总结

    T1

    ​ 题目大意:

    ​ 给定(n)个数, 将这(n)个数字放入两个集合(S,T)中, 不能为空集, 求(gcd(prod_{i in S} a_i, prod_{i in T} a_i) = 1)的方案数, 答案对(10^9+ 7)取模.(n <= 10^5, a_i <= 10^6).

    ​ 数学 + 并查集.

    ​ 假设没有题目的限制, 把(n)个数放入两个集合并且没有空集的方案数是 : (2^n - 2 = C_n^1 + C_n^2 + ... + C_n^{n - 1})

    ​ 加入了这个限制怎么办呢? 我们可以发现, 两个数有相同质因子的一定是要放在同一个集合里面的. 所以我们对具有相同质因子的数字连边, 最后求出联通块的个数(cnt), 那么答案就是 : (2^{cnt} - 2).

    ​ 其实可以用并查集来维护连通性, 可以降低复杂度.

    #include <bits/stdc++.h>
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch;
    	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    	return s * f;
    }
    
    const int N = 1e5 + 5, M = 1e6 + 5, mod = 1e9 + 7;
    int n, tag, cnt, ans;
    int p[M], fa[M], tmp[M], prime[N], is_prime[M];
    
    void make_pre() {
    	for(int i = 2;i < M; i++) {
    		if(!is_prime[i]) { prime[++ cnt] = i; p[i] = i; }
    		for(int j = 1;j <= cnt && i * prime[j] < M; j++) {
    			is_prime[i * prime[j]] = 1;
    			p[i * prime[j]] = prime[j];
    			if(!(i % prime[j])) break;
    		}
    	}
    }
    
    int ksm(int x, int y) {
    	int res = 1; while(y) { if(y & 1) res = 1ll * res * x % mod; x = 1ll * x * x % mod; y >>= 1; } return res;
    }
    
    int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
    
    int main() {
    
    	make_pre(); 
    	for(int T = read(); T ; T --) {
    		cin >> n; 
    		ans = cnt = 0;
    		for(register int i = 1;i <= M - 5; i++) fa[i] = i;
    		for(register int i = 1, x;i <= n; i++) {
    			x = read();
    			if(x == 1) { ans ++; continue ; }
    			int las = 0;
    			while(x >= 2) {
    				int xx = p[x]; tmp[++ cnt] = xx; 
    				while(!(x % xx)) x /= xx;
    				if(las) {
    					int fx = find(las), fy = find(xx);
    					if(fx != fy) fa[fx] = fy;
    				}
    				las = xx;
    			}
    		}
    		sort(tmp + 1, tmp + cnt + 1);
    		cnt = unique(tmp + 1, tmp + cnt + 1) - tmp - 1;
    		for(register int i = 1;i <= cnt; i++) if(fa[tmp[i]] == tmp[i]) ans ++;
    		printf("%d
    ", ksm(2, ans) - 2);
    	}
    
    	return 0;
    }
    

    T2

    ​ 题目大意 :

    ​ 给定(n)个节点(m)条边的无向图, 每条边都有一个权值(c = {0, 1}), 现在让你找一条长度为(d)的路径, 问不同的路径有多少, 两条路径不同是指着两条路径任意位置的权值不同, 也就是两个不同的01串.

    (n <= 90, m <= n*(n- 1), d <= 30).

    ​ meet in middle + 状压DP.

    ​ 首先这个(d)太大了, 肯定不能直接搜, 我们考虑把一条路径劈一半, 用两条长度为(frac{d}{2})的路径组合起来, 这是meet in middle.

    ​ 怎么找路径呢? $ dp[i][j]$ 表示从节点(u)出发((u)是我们枚举的), 是否存在一条状态为(i)的边到节点(j).(f[i][j])表示从(j)走是否存在一条状态为(i)的路径, 不管到那个节点.

    ​ 我们可以发现, 如果(dp[i][j])(f[i][j])都为1的话, 那么就存在一条合法路径, 中转点是(j), 但此时起点还不是1;

    ​ 我们从(n)到1枚举(u), 先不管怎么转移, 反正到枚举完(u)之后(dp)数组的含义就变为从1开始了, 然后按照我们上面说的找长度为(d)的路径.

    ​ 设(g0[i])是从(i)可以经过一条边权为0的边到的节点,, (g1[i])是从(i)可以经过一条边权为1的边到的节点 是一个二进制数, 可以用(bitset)存. 如果当前(dp[s][x] = 1), 说明存在一条从(u)(x)的路径, 路径的状态为(s).假设下一条边走的是边权为0的边 : (dp[s << 1] |= g0[x]), 说明存在一条状态为(s << 1)的路径从(u)到其他节点.那么走边权为1的节点也同理 : (dp[s << 1 | 1] |= g1[x]).

    ​ 转移(f)就更简单了, (f[s][u] = dp[1 << d1 | s].any()), (.any())(bitset)的函数, 看一个二进制数里是否有1.

    ​ 但是还有一个问题, 对于以这种状态的路径我们无法区分 : (101, 0101), 前者是长度为3的路径, 后者是长度为4的路径, 但是他们的二进制是一样的, 这就需要我们在他们前面补个1, 变成了:(1101, 10101), 这样就可以区分开了, 具体可以对照代码理解.

    #include <bits/stdc++.h>
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0,f = 1; char ch;
    	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    	return s * f;
    }
    
    const int N = 100, M = (1 << 20) + 1;
    int n, m, d, ans;
    bitset<N> g0[N], g1[N], f[M], dp[M]; 
    
    int main() {
    
    	n = read(); m = read(); d = read();
    	for(int i = 1, x, y, w;i <= m; i++) {
    		x = read(); y = read(); w = read();
    		if(w) g1[x][y] = g1[y][x] = 1;
    		else g0[x][y] = g0[y][x] = 1;
    	}
    	int d2 = d / 2, d1 = d - d2;
    	for(int u = n; u >= 1; u --) { //dp[i][j]表示从u出发是否存在一条状态为i的路径到j
    		for(int s = 0;s < M; s ++) dp[s].reset();
    		dp[1][u] = 1;
    		for(int s = 1;s < (1 << d1); s ++) 
    			for(int x = 1;x <= n; x ++)
    				if(dp[s][x]) {
    					dp[s << 1] |= g0[x]; dp[s << 1 | 1] |= g1[x];
    				}
    		for(int s = 0;s < (1 << d1); s ++) { //f[i][j]表示从j出发是否有一条状态为i的路径
    			f[s][u] = dp[1 << d1 | s].any();
    		}
    	}
    	for(int i = 0;i < (1 << d1); i++) 
    		for(int j = 0;j < (1 << d2); j++) if((dp[1 << d2 | j] & f[i]).any()) ans ++; //枚举中间节点
    	printf("%d", ans);
    
    	return 0;
    }
    

    T4

    题目链接

    ​ 我们可以发现, 最终的合法序列一定是一个单峰的.

    ​ 先说一下我考场上的错误思路 : 求出(f[i], g[i]), 然后枚举合并, (f[i])是从1到(i)变成单调不降序列的次数, (g[i])是从(i)(n)变成单调不升序列的次数.

    ​ 但是这么做是错误的, 比如这个样例 : 7 9 3 9 6 4 1 4 10 9;

    ​ 正确的答案最后应该是变成了 : 3 7 9 9 9 10 6 4 4 1, 而上面做法只能变成这样 : 3 7 9 9 10 9 6 4 4 1, 因为上面做法最后的9不可能会跑到10前面去.

    ​ 那怎么办呢? 我们可以知道, 当前数字变到正确的位置, 他肯定要与一些数字交换, 假设峰在当前数字的右侧, 那么它左侧比他大的数字一定会与它交换一次, 假设峰在当前数字的左侧, 那么它右侧侧比他大的数字一定会与它交换一次. 所以我们统计一个数字左边,右边比它大的数字的个数, 然后取较小的那个加到答案里就好了.

    #include <bits/stdc++.h>
    
    #define int long long
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch;
    	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    	return s * f;
    }
    
    const int N = 3e6 + 5;
    int n, a[N], b[N], c[N], t[N];
    long long ans, f[N], g[N];
    
    int lowbit(int x) { return x & -x; }
    
    void change(int x) { for( ; x < N ; x += lowbit(x)) t[x] ++; }
    
    long long query(int x) { long long res = 0; for( ; x ; x -= lowbit(x)) res += t[x]; return res; }
    
    signed main() {
    
    	n = read(); 
    	for(int i = 1;i <= n; i++) a[i] = b[i] = read();
    	sort(b + 1, b + n + 1);
    	int cnt = unique(b + 1, b + n + 1) - b - 1;
    	for(int i = 1;i <= n; i++) a[i] = lower_bound(b + 1, b + cnt + 1, a[i]) - b, c[n - i + 1] = a[i];
    	for(int i = 1;i <= n; i++) {
    		f[i] = i - 1 - query(a[i]); change(a[i]);
    	}
    	memset(t, 0, sizeof(t)); ans = 0;
    	for(int i = 1;i <= n; i++) {
    		g[i] = i - 1 - query(c[i]); change(c[i]);
    	}
    	for(int i = 1;i <= n; i++) ans += min(f[i], g[n - i + 1]);
    	printf("%lld
    ", ans);
    
    	return 0;
    }
    
  • 相关阅读:
    mybatis强化(二)Parameters和Result
    operator new 和 new operator
    hdu 1007 Quoit Design 分治求最近点对
    实现一个简单的shared_ptr
    bzoj 3224: Tyvj 1728 普通平衡树 替罪羊树
    bzoj 2648 SJY摆棋子 kd树
    hdu 2966 In case of failure k-d树
    codeforces 713D D. Animals and Puzzle 二分+二维rmq
    bzoj 1188 : [HNOI2007]分裂游戏 sg函数
    bzoj 1912 : [Apio2010]patrol 巡逻 树的直径
  • 原文地址:https://www.cnblogs.com/czhui666/p/14077488.html
Copyright © 2011-2022 走看看