2018杭电多校第二场

C

好久没有这么啃一份代码了,感觉网上这题的代码都是dls的各种改版2333

先说一下我对dls思路的理解吧:这道题问的是最少需要多少一笔画,那么就可以这么想:把这个图加最少的边完善成可以一笔画的图,然后跑欧拉路径就好,如果遇到新加的边,那么就说明这是一个新的一笔画。而加边的过程依据的是欧拉路的性质——一对奇数度的是可以的,再多的话就要连接一对奇数点让他们的度变成偶数。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 100100;
struct node{
	int v;
	int next;
	bool able;	
}edge[N * 4];//要多于总边数的4倍(*2双向边 并且可能加边) 
int head[N], cnt;
int n, m, res;//找到的路径数 
bool vis[N];
int deg[N]; //点的度数 
vector <int> st;  //保存一个联通块中度数为奇数的点 
vector <int> road[N];
void init(){
	res = 0;
	cnt = 1;
	memset(vis, 0, sizeof(vis));
	memset(head, 0, sizeof(head));
	memset(deg, 0, sizeof(deg));
}
void add(int u, int v){
	edge[++ cnt]. next = head[u];
	edge[cnt]. able = true;
	edge[cnt]. v = v;
	head[u] = cnt;
	deg[u] ++;
}
void add_Edge(int u, int v){
	add(u, v);
	add(v, u);
}
//找联通块以及度为奇数的点 
void dfs1(int s){
	vis[s] = true;
	if(deg[s] & 1)
		st. push_back(s);
	for(int i = head[s]; i; i = edge[i]. next){
		int v = edge[i]. v;
		if(! vis[v])
			dfs1(v);
	}
}
void dfs2(int s){
	for(int i = head[s]; i; i = edge[i]. next){
		if(edge[i]. able){
			edge[i]. able = edge[i ^ 1]. able = false;  //将这条边以及它的反向边标记为已使用 
			dfs2(edge[i]. v);
			if(i > 2 * m + 1) //说明此边是由奇数点添加得到,所以这条回路已经结束 
				res ++;
			else 
				road[res]. push_back((i / 2) * (2 * (i & 1) - 1)); //确定边的序号以及正负,偶数为负,奇数为正,因为偶数边是反向边 
		}
	}
}
int main(){
    while(~ scanf("%d %d", &n, &m)){
    	init();
    	for(int i = 0; i < m; i ++){
    		int u, v;
    		scanf("%d %d", &u, &v);
    		add_Edge(u, v);
		}
		for(int i = 1; i <= n; i ++){
			if(! vis[i] && deg[i]){
				dfs1(i); //找到联通块和奇数度数的顶点 
				if(st. empty()){  //孤立点 
					st. push_back(i);
					st. push_back(i);
				}
				for(int j = 2; j < st. size(); j += 2){ //从第二对开始的奇数顶点加一条边 ,因为如果只有一对奇数点,是可以完成欧拉路的 
					add_Edge(st[j], st[j + 1]);
				}
				res ++;
				dfs2(st[0]);
				st. clear();
			}
		}
		printf("%d\n", res);
		for(int i = 1; i <= res; i ++){
			printf("%d", road[i]. size());
			for(int j = 0; j < road[i]. size(); j ++){
				printf(" %d", road[i][j]);
			}
			puts("");
			road[i]. clear();
		}
	}
	return 0;
}

D

自己写几项就体会到了——先手必胜

#include <bits/stdc++.h>
using namespace std;

int main(){
	int n;
	while(cin >> n){
		cout << "Yes" << endl;
	}
	return 0;
}

E

一遇到构造题就有点懵,这题依然是。一开始都没搞清题目想让我干啥,被题面搞晕了

然后这份代码,emmm,用心体会。。

ps:dls直播中说的1的位置移动的解释:比如p = 5, 有这么一小组 10000,   1的位置是0,移动一1的位置就变成1,即01000

#include <bits/stdc++.h>
using namespace std;

int grid[3010][3010];

int main(){
	int p = 47;
	for(int i = 0; i < p; i ++){
		for(int j = 0; j < p; j ++){
			for(int k = 0; k < p; k ++){
				grid[i * p + j][k * p + (j * k + i) % p] = 1;   //行列数 = n*n 
			}
		}
	}
	puts("2000");
	for(int i = 0; i < 2000; i ++){
		for(int j = 0; j < 2000; j ++){
			printf("%d", grid[i][j]);
		}
		puts("");
	}
	return 0;
}

F

神仙题。

感觉这道题给我开了一扇窗——配合容斥的组合计数。

这题看大佬的讲解就是各种懵,??容斥公式怎么来的,??复杂度从n^4直接降到n^2

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 3030;
const ll mod = 998244353;

int m, n, a, b;
int pw[N * N], wz[N][N], s[N][N], fac[N];
ll inv[N], ans;
 
void init(){
    inv[0] = inv[1] = pw[0] = fac[1] = 1;
    for(int i = 1; i < N * N; i ++) 
        pw[i] = pw[i - 1] * 2 % mod;
    for(int i = 2; i < N; i ++)
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    for(ll i = 2; i < N; i ++){
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = inv[i - 1] * inv[i] % mod;
    }
    for(int i = 0; i < N; i ++){
        for(int j = i; j >= 0; j --){
            int delta = inv[j] * inv[i - j] % mod;
            if((i - j) & 1)
				delta = mod - delta;
            s[i][j] = s[i][j + 1] + delta;
            if(s[i][j] >= mod)
				s[i][j] -= mod;
        }
        for(int j = 0; j < N; j ++)
           wz[i][j] = pw[i * j] * inv[i] % mod * inv[j] % mod;
    }
}
 
int main(){
    init();
    while(~scanf("%d %d %d %d", &n, &m, &a, &b)){
        ans = 0;
        for(int w = 0; w <= n - a; w ++)
        for(int z = 0; z <= m - b; z++){
            ans = ans + 1ll * s[n - w][a] * s[m - z][b] % mod * wz[w][z] % mod;
            if(ans >= mod)
				ans -= mod;
        }
        printf("%lld\n", ans * fac[n] % mod * fac[m] % mod);
    }
    return 0;
}

G

这就算是线段树比较高端的操作了吧,通过维护几个值来巧妙地完成题目的要求

ps:顺便扒了个区间维护的模板,以前自己敲的那份太残废了

#include<bits/stdc++.h>
using namespace std;
#define Lson l, m, rt << 1
#define Rson m + 1, r, rt << 1 | 1
const int N = 1e5 + 5;

int n, q;
struct node{
    int cnt, lazy, minb, maxa;
}tree[N << 2];
int b[N];
void push_up(int rt){
    tree[rt]. minb = min(tree[rt << 1]. minb, tree[rt << 1 | 1]. minb);
    tree[rt]. cnt = tree[rt << 1]. cnt + tree[rt << 1 | 1]. cnt;
    tree[rt]. maxa = max(tree[rt << 1]. maxa, tree[rt << 1 | 1]. maxa);
}
void push_down(int rt){
    if(tree[rt]. lazy){
        int v = tree[rt]. lazy;
        tree[rt]. lazy = 0;
        tree[rt << 1]. maxa += v;
        tree[rt << 1 | 1]. maxa += v;
        tree[rt << 1]. lazy += v;
        tree[rt << 1 | 1]. lazy += v;
    }
}
void build(int l, int r, int rt){
    tree[rt]. lazy = 0;
    if(l == r){
        tree[rt]. cnt = tree[rt]. maxa = 0;
        tree[rt]. minb = b[l];
        return;
    }
    int m = l + r >> 1;
    build(Lson);
    build(Rson);
    push_up(rt);
}
void updata(int L, int R, int l, int r, int rt){
    if(L <= l && r <= R){
        tree[rt]. maxa ++;
        if(tree[rt]. maxa < tree[rt].minb){ //分子的最大值小于分母的最小值,没必要急着向下维护,打上懒惰标记 
            tree[rt]. lazy ++;
            return;
        }
        if(l == r && tree[rt]. maxa >= tree[rt]. minb){
            tree[rt]. cnt ++;
            tree[rt]. minb += b[l];  //最小值加上分母,相当于提高了+1的门槛,为下次+1做准备
            return;
        }
    }
    push_down(rt);
    int m = l + r >> 1;
    if(L <= m)
        updata(L, R, Lson);
    if(R > m)
        updata(L, R, Rson);
    push_up(rt);
}
int query(int L, int R, int l, int r, int rt){
    if(L <= l && r <= R){ //到达这一步前有很多前置条件,使得一定可以完成对待查询区间的精确覆盖 
        return tree[rt]. cnt;
    }
    int m = l + r >> 1;
    push_down(rt);
    int ans = 0;
    if(L <= m)
        ans += query(L, R, Lson);
    if(R > m)
        ans += query(L, R, Rson);
    return ans;
}
int main()
{
    int n, x, y;
    while(~ scanf("%d %d", &n, &q)){
        for(int i = 1; i <= n; i ++){
            scanf("%d", &b[i]);
        }
        build(1, n, 1);
        char pp[6];
        int l, r;
        while(q --){
            scanf("%s %d %d", pp, &l, &r);
            if(pp[0] == 'a'){
                updata(l, r, 1, n, 1);
            }
            else{
                printf("%d\n", query(l, r, 1, n, 1));
            }
        }
    }
    return 0;
}

J

因为交换相邻的两个数一定可以减少一个逆序对,所以这题的关键就是如何快速求出逆序对,我这里的是归并的方法

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll N = 500010;
ll a[N], t[N], ans;

void Merge_sort(ll l, ll r){
    if(l == r)
		return ;

    ll mid = (l + r) >> 1;
    Merge_sort(l, mid);   //得到左区间有序的数组 
    Merge_sort(mid + 1, r);  //得到右区间有序的数组 
    ll i = l, j = mid + 1, now = 0;
 
    while(i <= mid && j <= r){
        if(a[i] > a[j]){//归并排序是稳定排序,所以不写>=
            ans += (mid - i + 1); //mid + 1 到 j 都是有序的, i 到 mid 都是大于j的,共有mid - i + 1项,即逆序对的数目 
            t[++ now] = a[j ++];
        }
        else{
            t[++ now] = a[i ++];
        }
    }
    while(i <= mid)//(处理左右子区间中、指针未指到最后所剩下的元素)
        t[++ now] = a[i ++];
    while(j <= r)
        t[++ now] = a[j ++];
 
    now = 0;//(将两个子区间、经过有序+合并处理后得到的t[],赋值到对应位置的a[]中)
    for(ll k = l; k <= r; k ++)
        a[k] = t[++ now];
}
int main(){
    ll n, x, y;
    while(~ scanf("%lld %lld %lld", &n, &x, &y)){
        ans = 0;
        for(ll i = 1; i <= n; i ++)
            scanf("%lld", &a[i]);
        Merge_sort(1, n);
        if(x < y)
            printf("%lld\n", ans * x);
        else
            printf("%lld\n", ans * y);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38759433/article/details/81664100