WaWa的奇妙冒险(第十六周集训自闭现场)

第十六周周记(线段树与icpc/ccpc练习)

(一)线段树

Who Gets the Most Candies?
题意:一群小朋友玩游戏,第i个出列的人将会得到f(i)颗糖,f(i)为i的所有因子,游戏指定了谁先开始,且接下来的人按出去的人的牌上的数字选出(约瑟夫环)

思路:预处理出所有的f(i),然后通过线段树在O(logn)内查询下一个人的位置,对于因子数量一样的情况,按题目要求取前面那个人。

#include <iostream>
#include <cstring>
#include <cmath>
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int N = 5e5+10;

int sum[N << 2],f[N],card[N];
char name[N][12];

void init(){
	memset(f,0,sizeof(f));
	for(int i = 1;i*i <= N;++i){
		for(int j = i+1;i*j <= N;++j) f[i*j] += 2;
		f[i*i]++;
	}
	return ;
}

void pushup(int rt){
	sum[rt] = sum[rt << 1]+sum[rt << 1|1];
	return ;
}

void build(int rt,int l,int r){
	if(l == r){
		sum[rt] = 1;
		return ;
	}
	int m = (l+r) >> 1;
	build(lson);
	build(rson);
	pushup(rt);
	return ;
}

inline int query(int pos,int rt,int l,int r){
	if(l == r){
		sum[rt]--;
		return l;
	}
	int ans,m = (l+r) >> 1;
	if(pos <= sum[rt << 1]) ans = query(pos,lson);
	else ans = query(pos-sum[rt << 1],rson);
	pushup(rt);
	return ans;
}

int main()
{
	init();
	int n,k;
	while(~scanf("%d%d",&n,&k)){
		build(1,1,n);
		for(int i = 1;i <= n;++i) scanf("%s%d",name[i],&card[i]);
		
		int now,ans = -1,ansname,num = n;
		for(int i = 1;i <= n;++i){
			now = query(k,1,1,n);
			num--;
			
			if(f[i] > ans){
				ans = f[i];
				ansname = now;
			}
			
			if(!num) break;
			
			if(card[now] > 0) k = (k-2+card[now])%num+1;
			else k = ((k-1+card[now])%num+num)%num+1;
		}
		printf("%s %d\n",name[ansname],ans);
	}
	return 0;
}

Balanced Lineup
题意:问一个区间内,身高最高的奶牛和最低的奶牛差值为多少。

思路:线段树维护区间最值,每次查询query两次即可。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 5e4+10;
typedef long long ll;

int Max[maxn << 2],Min[maxn << 2];

void pushup(int rt){
	Max[rt] = max(Max[rt << 1],Max[rt << 1|1]);
	Min[rt] = min(Min[rt << 1],Min[rt << 1|1]);
	return ;
}

void build(int rt,int l,int r){
	if(l == r){
		scanf("%d",&Max[rt]);
		Min[rt] = Max[rt];
		return ;
	}
	int m = (l+r) >> 1;
	build(rt << 1,l,m);
	build(rt << 1|1,m+1,r);
	pushup(rt);
	return ;
}

inline int queryMax(int rt,int L,int R,int l,int r){
	if(L <= l && r <= R) return Max[rt];
	int ans = 0;
	int m = (l+r) >> 1;
	if(L <= m) ans = max(ans,queryMax(rt << 1,L,R,l,m));
	if(R > m) ans = max(ans,queryMax(rt << 1|1,L,R,m+1,r));
	return ans;
}

inline int queryMin(int rt,int L,int R,int l,int r){
	if(L <= l && r <= R) return Min[rt];
	int ans = INF;
	int m = (l+r) >> 1;
	if(L <= m) ans = min(ans,queryMin(rt << 1,L,R,l,m));
	if(R > m) ans = min(ans,queryMin(rt << 1|1,L,R,m+1,r));
	return ans;
}

int main()
{
	int n,q,l,r;
	while(~scanf("%d%d",&n,&q)){
		build(1,1,n);
		while(q--){
			scanf("%d%d",&l,&r);
			printf("%d\n",queryMax(1,l,r,1,n) - queryMin(1,l,r,1,n));
		}
	}
	return 0;
}

A Simple Problem with Integers
题意:区间修改和区间求和

思路:之前用线段树简单过了一次,然后看完蓝书之后发现树状数组的解法,大概理解为再开一个差分数组来实现区间修改。还没有完全理解透,但大概把代码码了出来。

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;

ll a[maxn],sum1[maxn],sum2[maxn];

inline ll lowbit(int x){
	return x&(-x);
}

void update(int pos,int val,int n){
	ll k = pos;
	for(;pos <= n;pos += lowbit(pos)){
		sum1[pos] += val;
		sum2[pos] += k*val;
	}
	return ;
}

inline ll getsum(int pos){
	ll res = 0,k = pos+1;
	for(;pos;pos -= lowbit(pos)) res += k*sum1[pos] - sum2[pos];
	return res;
}

int main()
{
	ll x,n,q,l,r,val;
	while(~scanf("%lld%lld",&n,&q)){
		a[0] = 0;
		memset(sum2,0,sizeof(sum2));
		memset(sum1,0,sizeof(sum1));
		for(ll i = 1;i <= n;++i){
			scanf("%lld",&x);
			a[i] = a[i-1]+x;
		}
		while(q--){
			char op[2];
			scanf("%s",op);
			if(op[0] == 'Q'){
				scanf("%lld%lld",&l,&r);
				printf("%lld\n",a[r]+getsum(r)-a[l-1]-getsum(l-1));
			}
			else{
				scanf("%lld%lld%lld",&l,&r,&val);
				update(l,val,n);
				update(r+1,-val,n);
			}
		}
	}
	return 0;
} 

Matrix
题意:矩阵区间修改和点查值。

思路:二维线段树,树上建树就完事了,因为这里实际上只有异或操作,所以我们可以利用lazy标记的思想,只标记lazy标记,在查询的过程中,看重复穿过lazy的标记标记有多少层,即可得到解。

#include <iostream>
#include <cstring>
#include <cmath>
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int N = 1e3+5;

int tree[N << 2][N << 2];
int n,ans = 0;

void buildY(int rtX,int rt,int l,int r){
	tree[rtX][rt] = 0;
	if(l == r) return ;
	int m = (l+r) >> 1;
	buildY(rtX,lson);
	buildY(rtX,rson);
	return ;
}

void build(int rt,int l,int r){
	buildY(rt,1,1,n);
	if(l == r) return ;
	int m = (l+r) >> 1;
	build(lson);
	build(rson);
	return ;
}

void updateY(int rtX,int rt,int l,int r,int y1,int y2){
	if(l >= y1 && r <= y2){
		tree[rtX][rt] ^= 1;
		return ;
	}
	int m = (l+r) >> 1;
	if(y1 <= m) updateY(rtX,lson,y1,y2);
	if(y2 > m) updateY(rtX,rson,y1,y2);
	return ;
}

void update(int rt,int l,int r,int x1,int x2,int y1,int y2){
	if(l >= x1 && r <= x2){
		updateY(rt,1,1,n,y1,y2);
		return ;
	}
	int m = (l+r) >> 1;
	if(x1 <= m) update(lson,x1,x2,y1,y2);
	if(x2 > m) update(rson,x1,x2,y1,y2);
	return ;
}

void queryY(int rtX,int rt,int l,int r,int y1){
	ans ^= tree[rtX][rt];
	if(l == r) return ;
	int m = (l+r) >> 1;
	if(y1 <= m) queryY(rtX,lson,y1);
	else queryY(rtX,rson,y1);
	return ;
}

void query(int rt,int l,int r,int x1,int y1){
	queryY(rt,1,1,n,y1);
	if(l == r) return ;
	int m = (l+r) >> 1;
	if(x1 <= m) query(lson,x1,y1);
	else query(rson,x1,y1);
	return ;
}

int main()
{
	int x1,x2,y1,y2,m,T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		build(1,1,n);
		
		char cmd;
		while(m--){
			getchar();
			cmd = getchar();
			if(cmd == 'C'){
				scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
				update(1,1,n,x1,x2,y1,y2);
			}
			else{
				scanf("%d%d",&x1,&y1);
				ans = 0;
				query(1,1,n,x1,y1);
				printf("%d\n",ans);
			}
		}
		if(T) printf("\n");
	}
	return 0;
}

Help with Intervals
题意:建立一个集合操作系统,具体操作此处不描述。

思路:刚开始毫无思路。。。,没想出怎么用线段树模拟,后面看了一眼题解,发现题解里面给出了各个操作的模拟方式,就尝试写了一下。

U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
S:[l,r]区间0/1互换
(摘自其他地方的题解)

因为此处有赋值和取反两个操作,姑且在维护一棵普通的涂色线段树的同时,再加一个lazy标记来记录取反,因为这次的线段树是有原值的,所以在下推时直接改变原值即可,无需像上一题那样在query中对每一层的lazy标记进行异或。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#define mid (l+r) >> 1
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
const int N = 65536 << 1;

int tre[N << 2],laz[N << 2],rev[N << 2];

void build(int rt,int l,int r){
	laz[rt] = -1;
	rev[rt] = tre[rt] = 0;
	if(l == r) return ;
	int m = mid;
	build(lson);
	build(rson);
	return ;
}

void pushdown(int rt){
	if(laz[rt] != -1){
		tre[rt << 1] = tre[rt << 1|1] = laz[rt << 1] = laz[rt << 1|1] = laz[rt];
		rev[rt << 1] = rev[rt << 1|1] = 0;
		laz[rt] = -1;
	}
	if(rev[rt]){
		rev[rt << 1] ^= 1;
		rev[rt << 1|1] ^= 1;
		rev[rt] = 0;
	}
	return ;
}

void update(int rt,int l,int r,int L,int R,int val){
	if(L > R) return ;
	if(L <= l && r <= R){
		if(val == 0 || val == 1){
			tre[rt] = laz[rt] = val;
			rev[rt] = 0;
		}
		else rev[rt] ^= 1;
		return ;
	}
	pushdown(rt);
	int m = mid;
	if(L <= m) update(lson,L,R,val);
	if(R > m) update(rson,L,R,val);
	return ;
}

inline int query(int rt,int l,int r,int pos){
	if(l == r){
		if(rev[rt]){
			tre[rt] ^= 1;
			rev[rt] ^= 1;
		}
		return tre[rt];
	}
	pushdown(rt);
	int m = mid;
	if(pos <= m) return query(lson,pos);
	else return query(rson,pos);
}

void put(int l,int r){
	printf("%c%d,",(l&1) ?'(' :'[',l/2);
	printf("%d%c",(r&1) ?(r+1)/2 :r/2,(r&1) ?')' :']');
	return ;
}

void print(){
	bool f = 0;
	int sum = 0,t;
	
	for(int i = 0;i < N;++i){
		int isalive = query(1,0,N,i);
		if(isalive && !f){
			t = i;
			f = 1;
		}
		else if(!isalive && f){
			if(sum++) printf(" ");
			put(t,i-1);
			f = 0;
		}
	}
	
	if(!sum) printf("empty set");
	printf("\n");
	return ;
}

int main()
{
	char op,pre,pos;
	int l,r;
	build(1,0,N);
	while(~scanf("%c%*c%c%d,%d%c%*c",&op,&pre,&l,&r,&pos)){
		l = pre == '[' ?(l << 1) :(l << 1)+1;
		r = pos == ']' ?(r << 1) :(r << 1)-1;
		
		if(op == 'U') update(1,0,N,l,r,1);
		else if(op == 'I'){
			update(1,0,N,0,l-1,0);
			update(1,0,N,r+1,N,0);
		}
		else if(op == 'D') update(1,0,N,l,r,0);
		else if(op == 'C'){
			update(1,0,N,0,l-1,0);
			update(1,0,N,r+1,N,0);
			update(1,0,N,l,r,-1);
		}
		else if(op == 'S') update(1,0,N,l,r,-1);
	}
	print();
	return 0;
}

Can you answer these queries?
题意:对区间内所有的值求根,然后求区间和。

思路:刚开始感觉很无解,然后尝试每次的更新都更新到叶子节点,纯暴力写法。。。,然后tle了,最后看了网上题解,很巧妙的用区间和等于区间长度的操作实现了线段树上的剪枝?(剪枝这个词有点怪),确实很厉害。

#include <iostream>
#include <cstring>
#include <cmath>
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;

ll sum[maxn << 2];

void pushup(int rt){
	sum[rt] = sum[rt << 1]+sum[rt << 1|1];
	return ;
}

void build(int rt,int l,int r){
	if(l == r){
		scanf("%lld",&sum[rt]);
		return ;
	}
	int m = (l+r) >> 1;
	build(lson);
	build(rson);
	pushup(rt);
	return ;
}

void update(int rt,int l,int r,int L,int R){
	if(l == r){
		sum[rt] = sqrt(sum[rt]);
		return ;
	}
	int m = (l+r) >> 1;
	if(L <= l && r <= R && sum[rt] == r-l+1) return ;
	if(L <= m) update(lson,L,R);
	if(R > m) update(rson,L,R);
	pushup(rt);
	return ;
}

inline ll query(int rt,int l,int r,int L,int R){
	if(L <= l && r <= R) return sum[rt];
	ll ans = 0;
	int m = (l+r) >> 1;
	if(L <= m) ans += query(lson,L,R);
	if(R > m) ans += query(rson,L,R);
	return ans;
}

int main()
{
	int cmd,l,r,n,q,cas = 0;
	while(~scanf("%d",&n)){
		build(1,1,n);
		
		scanf("%d",&q);
		printf("Case #%d:\n",++cas);
		while(q--){
			scanf("%d%d%d",&cmd,&l,&r);
			if(l > r) swap(l,r);
			if(cmd) printf("%lld\n",query(1,1,n,l,r));
			else update(1,1,n,l,r);
		}
		printf("\n");
	}
	return 0;
} 

Atlantis
题意:求面积并。

思路:经典的扫描线问题,蓝书上面看见了才去做的。大部分都能理解,但对于左闭右开这个区间设定仍有异或。个人认为可能存在某种情况导致非重合区间重合,然后无法及时删去,但构思不出样例。以及个人认为,左闭右开的想法实际上是吧线段映射成了点,从而避免了所谓的重合。

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#define mid (l+r) >> 1;
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
typedef long long ll;
const int N = 1e2+5;

struct edg{
    double y1,y2,x;
    int val;
    edg(){}
    edg(double y1,double y2,double x,int val):y1(y1),y2(y2),x(x),val(val){}
    bool operator < (const edg a){
        return x < a.x;
    }
} e[N << 1];
int s[N << 3];
double lsh[N << 1],sum[N << 3];

void pushup(int rt,int l,int r){
    if(s[rt]) sum[rt] = lsh[r+1]-lsh[l];
    else if(l == r) sum[rt] = 0;
    else sum[rt] = sum[rt << 1]+sum[rt << 1|1];
    return ;
}

void build(int rt,int l,int r){
    sum[rt] = s[rt] = 0;
    if(l == r) return ;
    int m = mid;
    build(lson);
    build(rson);
    return ;
}

void update(int rt,int l,int r,int L,int R,int val){
    if(L <= l && r <= R){
        s[rt] += val;
        pushup(rt,l,r);
        return ;
    }
    int m = mid;
    if(L <= m) update(lson,L,R,val);
    if(R > m) update(rson,L,R,val);
    pushup(rt,l,r);
    return ;
}

int main()
{
    int tot,n,cas = 0;
    double x1,x2,y1,y2;
    while(scanf("%d",&n) && n){
        tot = 0;
        for(int i = 1;i <= n;++i){
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            edg t(y1,y2,x1,1);e[++tot] = t;lsh[tot] = y1;
            edg p(y1,y2,x2,-1);e[++tot] = p;lsh[tot] = y2;
        }
        sort(e+1,e+tot+1);
        sort(lsh+1,lsh+tot+1);
        int cnt = unique(lsh+1,lsh+tot+1)-lsh-1;
        
        build(1,1,cnt);
        
        double ans = 0;
        for(int i = 1;i < tot;++i){
            int l = lower_bound(lsh+1,lsh+cnt+1,e[i].y1)-lsh;
            int r = lower_bound(lsh+1,lsh+cnt+1,e[i].y2)-lsh-1;
            update(1,1,cnt,l,r,e[i].val);
            ans += (e[i+1].x-e[i].x)*sum[1];
        }
        printf("Test case #%d\n",++cas);
        printf("Total explored area: %.2f\n\n",ans);
    }
    return 0;
} 

(二)icpc/ccpc签到题

Misunderstood … Missing Gym - 102056I
题意:你拥有两个属性A和D(初始为0),A为当前攻击力,D为每回合成长的攻击力,且在每个回合拥有三种选择,a[i]攻击并造成A+a[i]点伤害,使D增加b[i],使A增加c[i]。

思路:倒着dp,我们可以发现,对于三种可以转换为对伤害的贡献,操作一直接计算贡献即可,而操作二三则根据后面的攻击次数得到贡献,而攻击的次数和攻击的轮次可以是不定的,可以用区间覆盖的方式dp,做到拆分效果。

总结一下:
dp的三个因素:
1.第i回合
2.攻击j次
3.在第k个区间攻击

综上所述,三维dp,为了缩减空间,采用滚动数组优化即可。

#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
#include <list>
#include <set>
#include <stdio.h>
#include <fstream>
#include <cassert>
#include <map>
using namespace std;
typedef long long ll;
const int N = 1e2+5;

ll a[N],b[N],c[N];
ll dp[2][N][5100];

int main() {
    int n,T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i = 1;i <= n;++i) scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);

        memset(dp,0,sizeof(dp));
        int now = 0;
        dp[now^1][1][n] = a[n];

        for(int i = n-1;i >= 1;--i){
            for(int j = 1;j <= n-i;++j){
                ll s = (i+i+j-1)*j/2,e = (n+n-j+1)*j/2;
                for(int k = s;k <= e;++k){
                    dp[now][j+1][k+i] = max(dp[now][j+1][k+i],dp[now^1][j][k]+a[i]);
                    dp[now][j][k] = max(dp[now][j][k],dp[now^1][j][k]+j*c[i]);
                    dp[now][j][k] = max(dp[now][j][k],dp[now^1][j][k]+(k-i*j)*b[i]);
                }
            }
            now ^= 1;
        }

        ll ans = 0;
        for(int i = 1;i <= n;++i){
            for(int j = 1;j <= 5100;++j){
                ans = max(dp[now^1][i][j],ans);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

Pastoral Life in Stardew Valley Gym - 102055G
题意:可以分一块放n*m的稻草人,但要保证稻草人被草包围,要对1e9+7取模。

思路:感觉是一个递推找规律的题目,推了一下3*n的小规模,忽然发现貌似是杨辉三角里的一条斜线C(3,n)+C(4,n),队友猜测矩形即是行贡献和列贡献的乘积,尝试写了一发交,wa在了精度了,改了点细节long long,成功ac。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1e9+7;

inline qpow(ll a,ll b){
	ll res = 1;
	a %= MOD;
	while(b){
		if(b & 1) res = (res*a) % MOD;
		a = (a*a) % MOD;
		b >>= 1;
	}
	return res % MOD;
}
inline ll inv(ll x){
	return qpow(x,MOD-2);
}

inline ll C(ll m,ll n){
	if(m > n) return 0ll;
	if(m == n || m == 0) return 1ll;
	
	ll up = 1,down = 1;
	for(ll i = n-m+1;i <= n;++i) up = (up*i) % MOD;
	for(ll i = 1;i <= m;++i) down = (down*i) % MOD;
	return (up*inv(down)) % MOD;
}

int main()
{
	int T,cas = 0;
	ll r,c;
	scanf("%d",&T);
	while(T--){
		scanf("%lld%lld",&r,&c);
		if(r < 3 || c < 3) printf("Case %d: 0\n",++cas);
		else{
			ll ans = (C(3,r)+C(4,r))%MOD;
			ll ans2 = (C(3,c)+C(4,c))%MOD;
			printf("Case %d: %lld\n",++cas,((ans*ans2)%MOD+MOD)%MOD);
		}
	}
	return 0;
}

Mr. Panda and Kakin Gym - 102055K
题意:RAS解密,得到明文。。。没学过密码学,也没看懂题意,百度了一下RAS发现有现成的推的公式。。。

思路:公式已经出来了,但有个问题,数据规模太大,尝试__int 128,发现每个编译器都不支持,然后写了一个快速积,tle。。。看题解表示要用O(1)复杂度的快速积才行。。。

#include <bits/stdc++.h>
using namespace std;
typedef long double ld;
typedef long long ll;

inline ll mul(ll a,ll b,ll c){return (a*b-(ll)((ld)a*b/c)*c+c)%c;}
inline ll qpow(ll a,ll b,ll n){
	ll res = 1ll;
	while(b){
		if(b & 1) res = mul(res,a,n);
		a = mul(a,a,n);
		b >>= 1;
	}
	return res % n;
}

inline ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b == 0){
		x = 1ll;
		y = 0ll;
		return a;
	}
	ll d = exgcd(b,a%b,y,x);
	y -= a/b*x;
	return d;
}

int main()
{
	int T,cas = 0;
	ll c,n,e,p,q,pq,d,y;
	scanf("%d",&T);
	while(T--){
		scanf("%lld%lld",&n,&c);
		e = (1 << 30)+3;
		p = sqrt(n);
		while(n % p != 0) p--;
		q = n/p;
		pq = (p-1)*(q-1);
		exgcd(e,pq,d,y);
		d = ((d%pq)+pq)%pq;
		
		ll ans = qpow(c,d,n);
		printf("Case %d: %lld\n",++cas,(ans+n)%n);
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值