2018杭电多校第三场

A

被题意给搞蒙了。。

这题的关键就是求出一个滚动区间内的最大值以及最大值更新了多少次。

可以考虑单调队列反向用:单调队列维护最小值,数组倒着入队,那么显然,队首元素就是最大值,然后队内元素个数就是更新了多少次。因为假设x是最大值,那么x之前的元素一定会在队列中,即x更新了队列中元素个数的次数

#include
typedef long long ll;
using namespace std;
ll a[10000005], Q[10000005];
int main()
{
	int T;
	scanf("%d", &T);
	while(T --){
		ll n, m, k, p, q, r, mod;
		scanf("%lld %lld %lld %lld %lld %lld %lld", &n, &m, &k, &p, &q, &r, &mod);
		for(ll i = 1; i <= k; i ++)
			scanf("%lld", &a[i]);
		for(ll i = k + 1; i <= n; i ++)
			a[i] = (p * a[i - 1] + q * i + r) % mod;
			
		int rear = 0, head = 1;
		ll ans1 = 0, ans2 = 0;
		for(ll i = n; i > 0; i --){
			while(rear >= head && a[Q[rear]] <= a[i]) //确定插入位置 
				rear --;
			Q[++ rear] = i;
			if(i + m - 1 <= n){ //还可以产生出长度为m的区间 
				while(Q[head] >= i + m) //缩头 
					head ++;
				ans1 += a[Q[head]] ^ i;
				ans2 += (rear - head + 1) ^ i;
			}
		}
		printf("%lld %lld\n", ans1, ans2);
	}
	return 0;
}

 

C

这题对我来说好像有点难。。感觉我补这题的收获就是知道状压dp是什么东西了。一个东西选中与否显然可以用01表示,不使用开bool数组而是借助一个整数的各个位来做这件事,应该就是状压dp了。然后剩下的就是各种神仙操作了。。

#include 
using namespace std;

typedef long long  ll;
const ll mod = 1000000007;
 
int n, q, u, v, t;
 
int dp[1234][20], f[1234];
char s[20]; 
 
int main(){
	for(int i = 1; i < 1024; i ++)
		f[i] = f[i >> 1] + (i & 1);//初始化,f[i]存储i的二进制有多少个1 
	
	scanf("%d",&t);
	while(t --){
		scanf("%d %d", &n, &q);
		memset(dp, 0, sizeof(dp));
		
		for(int i = 0; i < (1 << n); i ++)
			dp[i][0] = 1;         // 边数为0的状态有1种,初始化为1 
		
		for(int i = 0; i < q; i ++){
			scanf("%s %d %d", s, &u, &v);
			u --, v --;//若输入 1,2 要状态表示
			// 1:第一位二进制为1,   2:第二位二进制为1
			//1=2的0次方     1=2的一次方 所以减减 
			//很神奇对吧,u减减后2的(u-1)的次方二进制下 1所在的位就是第u位 
		int Ss = (1 << u) + (1 << v); //上面减减后状态表示 
		     
		for(int S = 0; S <= (1 << n) - 1; S ++){
		//枚举各种点集状态 
		    if((S & Ss) == Ss){//若这种状态包含输入的两个点则处理 
			    for(int j = 1; j <= f[S]; j ++){
			       	dp[S][j] += (s[0] == '+' ? dp[S ^ Ss][j - 1] : (mod - dp[S ^ Ss][j - 1]));
			       	// dp[S^Ss][j-1] 是不包含这两个点 且边数减少一 
					if(dp[S][j] >= mod)
						dp[S][j] -= mod; 
				}
			}	
     	}
		for(int j = 1; j <= n / 2; j ++)
		  printf("%d%c", dp[(1 << n) - 1][j], " \n"[j == n / 2]);	    
	    }
	}
  	return 0;	
}

 

F

很棒的一道题,我做的时候一看到异或基本就被劝退了

 

A:Q的异或和,B:T的异或和,S:总的异或和

显然A^B = S,那么当S = 0时,也就意味着A=B,即平局

当S!=0是,那么不妨设S = 00001xxxxxxx。设1的位置为i,那么显然,在i之前的每一个位置,A的值与B的值相同,在i这个位置,有一个是1有一个是0.那么显然,先手的Q只要把该位为1的数取走一个就一定会获胜

#include 
using namespace std;

int main(){
	int t;
	scanf("%d", &t);
	while(t --){
		int n;
		int x;
		int sum = 0;
		cin >> n;
		for(int i = 0; i < n; i ++){
			scanf("%d", &x);
			sum ^= x;
		}
		int a, b;
		for(int i = 0; i < n - 1; i ++)
			scanf("%d %d", &a, &b);
		if(sum == 0){
			cout << 'D' << endl;
		}
		else{
			cout << 'Q' << endl;
		}
	}
	return 0;
}

 

G

这题乍一看我都想跑最短路了,然后经过大佬的层层分析后才知道,原来就是求一个凸包,点技能树咯~~

#include
using namespace std;

const int N = 2e5 + 10;
typedef long long ll;

int n, m;
int top, ma;
int ans[N];
struct node{
    int x, y;
    int pos;
}s[N], sta[N], w;

ll judge(node p1, node p2, node p0){		//面积公式判断正负值 
    ll ans = 1ll * (p1. x - p0. x) * (p2. y - p0. y) - 1ll * (p2. x - p0. x) * (p1. y - p0. y);
    return ans; 
}
bool cmp(node a, node b){
    ll c = judge(w, b, a);//极角排序,同角度按距离从小到大排 
    if(b. x == a. x && b. y == a. y)
		return a. pos > b. pos;
    if(! c)
		return pow(a. x - w. x, 2) + pow(a. y - w. y, 2) < pow(b. x - w. x, 2) + pow(b. y - w. y, 2);
    return c > 0;
}
void Graham(){
    sta[0] = s[0], sta[1] = s[1];
    top = 2;
    for(int i = 2; i < n; i ++){
        while(top > 1 && judge(sta[top-2], s[i], sta[top - 1]) <= 0){
            if(judge(sta[top-2], s[i], sta[top-1]) < 0 || sta[top - 1]. pos > s[i]. pos)
				top --;
            else break;
        }
        sta[top ++] = s[i];
    }
    for(int i = 0; i < top; i ++)
		printf("%d%c", sta[i]. pos + 1, i == top - 1 ? '\n' : ' ');
}
int main(){
    int t;
    scanf("%d", &t);
    while(t --){
        scanf("%d", &n);
        for(int i = 0; i < n; i ++){
            scanf("%d %d", &s[i]. x, &s[i]. y);
            s[i]. pos = i; 
        }
        w = s[0];
        sort(s + 1, s + n, cmp);
        Graham();
    }
    return 0;
} 

 

H

又是一道流水线贪心问题,这次dls给出了很好的证明:

将怪兽分成两类:a < b 的和 a≥b 的,前一类打完会加 血,后一类打完会扣血,显然最优策略下应该先打第一类再 打第二类。 对于 a < b 的怪兽,显然最优策略下应该按照 a 从小到大打

对于 a≥b 的怪兽,考虑两只怪兽 i,j,先打 i 再打 j 的过程 中血量会减少到 HP+min(−ai,−ai +bi−aj),因为 a≥b, 所以这等于 HP−ai−aj +bi。同理先打 j 再打 i 的过程中血 量会减少到 HP−ai−aj +bj。 可以发现按照任何顺序都只和 b 有关,最优策略下需要让 血量尽可能多,因此要按照 b 从大到小打

 

#include 
using namespace std;

typedef long long ll;
const int N = 100100;
struct Node {
    ll a, b;
    int fa, Index;
};

bool operator<(const Node &a, const Node &b) {
    bool flag_a = a.a < a.b;
    bool flag_b = b.a < b.b;
    if(flag_a != flag_b) {
        return flag_a < flag_b;
    }
    if(flag_a) {
        return a.a > b.a;
    }
    return a.b < b.b;
}
bool operator!=(const Node &a, const Node &b) {
    return a.a != b.a || a.b != b.b || a.fa != b.fa || a.Index != b.Index;
}
int T, n, u, v;
int fa[N];
Node node[N];
vector G[N];
priority_queue que;

void Init() {
    for(int i = 1; i <= n; ++i) {
        fa[i] = i;
        G[i].clear();
    }
}

int Find(int x) {
    return x == fa[x]? x: fa[x] = Find(fa[x]);
}

void unit(int x, int y) {
    int xx = Find(x);
    int yy = Find(y);
    fa[xx] = yy;
}

void dfs(int f, int x) {	//转有根树 
    node[x].fa = f;
    int len = G[x].size();
    for(int i = 0; i < len; ++i) {
        int pos = G[x][i];
        if(pos != f) {
            dfs(x, pos);
        }
    }
}
int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        Init();
        node[1].a = node[1].b = 0;
        node[1].Index = 1;
        for(int i = 2; i <= n; ++i) {
            scanf("%I64d%I64d", &node[i].a, &node[i].b);
            node[i].Index = i;
        }
        for(int i = 1; i < n; ++i) {
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dfs(1, 1);
        for(int i = 2; i <= n; ++i) {
            que.push(node[i]);
        }
        while(!que.empty()) {
            Node tmp = que.top();
            que.pop();
            if(node[tmp.Index] != tmp || tmp.Index == 1) { //如果该结点已被更新或者该结点是根结点 
                continue;
            }            
			int f = Find(tmp.fa);
            unit(tmp.Index, f);
            ll a = -min(-node[f].a, -node[f].a + node[f].b - tmp.a);
            ll b = -node[f].a + node[f].b - tmp.a + tmp.b + a;
            node[f].a = a;
            node[f].b = b;
            que.push(node[f]);
        }
        printf("%I64d\n", node[1].a);
    }
    return 0;
}

 

I

当成一座需要攀爬的山峰吧,以后要啃掉这道题

#include
using namespace std;

const int mx = 1e2 + 5;
const int mod = 1e9 + 7;
typedef long long ll;
int n, N;
int a[mx], gcd[mx][mx], b[mx];
int id[mx][mx][mx], w[1500][mx];
int v[1500][mx];
ll f[mx][1500];

ll qpow(ll x, ll y){
    ll ans = 1;
    while(y){
        if(y & 1) 
			ans = ans * x % mod;
        y >>= 1;
        x = x * x % mod;
    } 
    return ans;
}
int main(){
    int t;
    scanf("%d", &t);
    while(t --){
        scanf("%d %d", &n, &N);
        memset(f, 0, sizeof(f));
        for(int i = 1; i <= n; i ++)
			scanf("%d", a + i);
        for(int i = 1; i <= N; i ++)
			scanf("%d", b + i);
        for(int i = 1; i <= N; i ++)
        for(int j = 1; j <= N; j ++)
			gcd[i][j] = __gcd(i, j);
        int cnt = 0;
        for(int i = 1; i <= N; i ++) 
            for(int j = i; j <= N; j += i)
                for(int k = j; k <= N; k += j)
					id[i][j][k] = ++ cnt;
        for(int i = 1; i <= N; i ++)
            for(int j = i; j <= N; j += i)
                for(int k = j; k <= N; k += j){
                    int x = id[i][j][k];
                    for(int y = 1; y <= N; y ++)
                    	w[x][y] = id[gcd[j][y]][gcd[k][y]][y], v[x][y] = b[gcd[i][y]];
                }
        for(int i = 1; i <= N; i ++){
            for(int j = 1; j <= N; j ++){
                for(int k = 1; k <= N; k ++){
                    if(a[1] && i != a[1])
						continue;
                    if(a[2] && j != a[2]) 
						continue;
                    if(a[3] && k != a[3])
						continue;
                    int u = gcd[gcd[i][j]][k];
                    f[3][id[u][gcd[j][k]][k]] ++;
                }
            }
        }
        for(int i = 3; i < n; i ++){
            for(int j = 1; j <= cnt; j ++){
                if(f[i][j]){
	                for(int k = 1; k <= N; k ++){
	                    if(a[i + 1] && k != a[i + 1])
							continue;
	                    f[i + 1][w[j][k]] = (f[i + 1][w[j][k]] + f[i][j] * v[j][k]) % mod;
	                }
	            }
            }
        } 
        ll ans = 0,c = 0;
        for(int i = 1; i <= cnt; i ++)
			ans = (ans + f[n][i]) % mod;
        for(int i = 1; i <= n; i ++) 
			if(! a[i]) 
				c ++;
        printf("%lld\n", ans * qpow(qpow(N, c), mod - 2) % mod);
    }
    return 0;
}

 

M

看了题解之后,思路还是比较清晰的,用分块降复杂度什么的真的是。。

明天就是七夕了,emmm,虽然和我没什么关系,但是出于强迫症,总感觉今天自己必须把第三场给补完,emmm,或者说初步补完。

#include 
using namespace std;

typedef long long ll;
const int Size = 51;
const int maxn = 101;
int n;
struct Matrix {
    int num[Size][Size];
    void Init() {
        for(int i = 1; i <= n; ++i) {
            memset(num[i], 0x3f, sizeof(int) * (n + 1));
        }
    }
    void Set_zero() {
        for(int i = 1; i <= n; ++i) {
            num[i][i] = 0;
        }
    }
    void operator=(const Matrix &m) {
        for(int i = 1; i <= n; ++i) {
            memcpy(num[i], m.num[i], sizeof(int) * (n + 1));
        }
    }
    void Combine(const Matrix &m, Matrix &ans) {
        Matrix ret;
        ret.Init();
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= n; ++j) {
                for(int k = 1; k <= n; ++k) {
                    ret.num[i][j] = min(ret.num[i][j], num[i][k] + m.num[k][j]);
                }
            }
        }
        ans = ret;
    }
    void floyd() {
        for(int k = 1; k <= n; ++k) {
            for(int i = 1; i <= n; ++i) {
                for(int j = 1; j <= n; ++j) {
                    num[i][j] = min(num[i][j], num[i][k] + num[k][j]);
                }
            }
        }
    }
};
int T, m, u, v, dis, q, k, INF, ans;
Matrix A[maxn], B[maxn], G;
int main() {
    memset(&INF, 0x3f, sizeof(int));
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &m);
        G.Init();
        for(int i = 0; i < m; ++i) {
            scanf("%d%d%d", &u, &v, &dis);
            G.num[u][v] = min(G.num[u][v], dis);
        }
        A[0].Init();
        A[0].Set_zero();
        B[0].Init();
        B[0].Set_zero();
        for(int i = 1; i < maxn; ++i) {
            B[i - 1].Combine(G, B[i]);
        }
        for(int i = 1; i < maxn; ++i) {
            A[i - 1].Combine(B[100], A[i]);
        }
        G.Set_zero();
        G.floyd();
        for(int i = 0; i < maxn; ++i) {
            G.Combine(B[i], B[i]);
        }
        scanf("%d", &q);
        while(q--) {
            scanf("%d%d%d", &u, &v, &k);
            ans = INF;
            for(int i = 1; i <= n; ++i) {
                ans = min(ans, A[k / 100].num[u][i] + B[k % 100].num[i][v]);
            }
            if(ans == INF) {
                printf("-1\n");
            } else {
                printf("%d\n", ans);
            }
        }
    }
    return 0;
}

 

你可能感兴趣的:(2018杭电多校第三场)