• 20联赛集训day9 题解


    A

    考虑枚举右端点,维护所有左端点的答案:只需要处理出 (pre_i) 表示 (i) 前面第一个和它一样的位置,每次让 ((pre_i,i]) 加上 (w)((pre_{pre_i},pre_i]) 减去 (w) 即可。

    #pragma GCC optimize("Ofast")
    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    inline char nc(){
        #define SIZE 1000000+3
        static char buf[SIZE],*p1 = buf+SIZE,*p2 = buf+SIZE;
        if(p1 == p2){
            p1 = buf;p2 = buf+fread(buf,1,SIZE,stdin);
            if(p1 == p2) return -1;
        }
        return *p1++;
        #undef SIZE
    }
    
    template <typename T>
    inline void read(T &x){
        x = 0;int flag = 0;char ch = nc();
        while(!isdigit(ch)){
            if(ch == '-') flag = 1;
            ch = nc();
        }
        while(isdigit(ch)){
            x = (x<<1) + (x<<3) + (ch^'0');
            ch = nc();
        }
        if(flag) x = -x;
    }
    
    const int MAXN = 1e6 + 5;
    LL mx[MAXN<<2],tag[MAXN<<2];
    #define lc ((x)<<1)
    #define rc ((x)<<1|1)
    
    inline void cover(int x,LL d){
    	mx[x] += d;tag[x] += d;
    }
    
    inline void pushdown(int x){
    	if(tag[x]){
    		cover(lc,tag[x]);
    		cover(rc,tag[x]);
    		tag[x] = 0;
    	}
    }
    
    inline void modify(int x,int l,int r,int L,int R,int d){
    	if(l == L && r == R) return cover(x,d);
    	int mid = (l + r) >> 1;pushdown(x);
    	if(R <= mid) modify(lc,l,mid,L,R,d);
    	else if(L > mid) modify(rc,mid+1,r,L,R,d);
    	else modify(lc,l,mid,L,mid,d),modify(rc,mid+1,r,mid+1,R,d);
    	mx[x] = std::max(mx[lc],mx[rc]);
    }
    
    int n,m,c[MAXN],w[MAXN];
    int lst[MAXN],pre[MAXN];
    
    int main(){
    	read(n);read(m);
    	FOR(i,1,n) read(c[i]);
    	FOR(i,1,m) read(w[i]);
    	LL ans = 0;
    	FOR(i,1,n){
    		pre[i] = lst[c[i]];lst[c[i]] = i;
    		int p1 = pre[i],p2 = pre[p1];
    		modify(1,1,n,p1+1,i,w[c[i]]);
    		if(p1) modify(1,1,n,p2+1,p1,-w[c[i]]);
    		ans = std::max(ans,mx[1]);
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    B

    观察一下距离一个点直接距离为 (d) 的图像是啥:

    倾斜不是很好处理,于是我们旋转 (45) 度,现在直接距离定义变成了 (min(x_i-x_j,y_i-y_j))

    然后我彩笔直接去扫描线,结果爆零了。。

    我们先考虑如何求出距离最小值:发现这只可能会在相邻的 (x) 坐标或者 (y) 坐标产生。我们先将直接距离为 (0) 的点都并起来,然后将点按照 (x) 坐标排序,相邻的权值判断一下如果在不同连通块就能对答案产生贡献。

    判断 (-1) 只需要判断连通块大小是否是 (1) 即可。

    那么现在如何去计算方案数呢?一个 (O(n^2)) 的想法是数连通块之间有多少条边,去重后每条边贡献是连通块大小乘积。我考试的时候自闭了。。

    但是实际上发现这样的边只有 (O(n)) 条:因为只会在按照某一维排序后相邻的点对中产生,所以去个重统计答案就行了。

    发现就是没去写距离的式子。。被降智了。。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 2e5 + 5;
    int n;
    
    struct Node{
    	int x,y,id;
    	Node(int x=0,int y=0,int id=0) : x(x),y(y),id(id) {}
    }a[MAXN];
    
    int f[MAXN],sz[MAXN];
    
    inline int find(int x){
    	return x == f[x] ? x : f[x] = find(f[x]);
    }
    
    inline void merge(int x,int y){
    	x = find(x);y = find(y);
    	if(x == y) return;
    	f[x] = y;
    }
    
    std::map<P,int> S;
    
    inline void addedge(int x,int y){
    	x = find(x);y = find(y);
    	if(x == y) return;
    	if(x > y) std::swap(x,y);
    	S[MP(x,y)] = 1;
    }
    
    int main(){
    	scanf("%d",&n);
    	FOR(i,1,n){
    		int x,y;scanf("%d%d",&x,&y);
    		a[i] = Node(x+y,x-y,i);f[i] = i;
    	}
    	std::sort(a+1,a+n+1,[&](const Node &x,const Node &y){return x.x < y.x;});
    	for(int l = 1,r;l <= n;l = r+1){
    		r = l;
    		while(r+1 <= n && a[r+1].x == a[r].x) ++r;
    		FOR(i,l+1,r) merge(a[i].id,a[i-1].id);
    	}
    	std::sort(a+1,a+n+1,[&](const Node &x,const Node &y){return x.y < y.y;});
    	for(int l = 1,r;l <= n;l = r+1){
    		r = l;
    		while(r+1 <= n && a[r+1].y == a[r].y) ++r;
    		FOR(i,l+1,r) merge(a[i].id,a[i-1].id);
    	}
    	std::sort(a+1,a+n+1,[&](const Node &x,const Node &y){return x.x < y.x;});
    	int ans = 1e9;
    	for(int l = 1,r;l <= n;l = r+1){
    		r = l;
    		while(r+1 <= n && a[r+1].x == a[r].x) ++r;
    		if(l-1 && find(a[l-1].id) != find(a[l].id)) ans = std::min(ans,a[l].x-a[l-1].x);
    	}
    	std::sort(a+1,a+n+1,[&](const Node &x,const Node &y){return x.y < y.y;});
    	for(int l = 1,r;l <= n;l = r+1){
    		r = l;
    		while(r+1 <= n && a[r+1].y == a[r].y) ++r;
    		if(l-1 && find(a[l-1].id) != find(a[l].id)) ans = std::min(ans,a[l].y-a[l-1].y);
    	}
    	if(ans == 1e9){
    		puts("-1");
    		return 0;
    	}
    	printf("%d
    ",ans);
    	std::sort(a+1,a+n+1,[&](const Node &x,const Node &y){return x.x < y.x;});
    	for(int l = 1,r;l <= n;l = r+1){
    		r = l;
    		while(r+1 <= n && a[r+1].x == a[r].x) ++r;
    		if(l-1 && a[l].x-a[l-1].x == ans) addedge(a[l-1].id,a[l].id);
    	}
    	std::sort(a+1,a+n+1,[&](const Node &x,const Node &y){return x.y < y.y;});
    	for(int l = 1,r;l <= n;l = r+1){
    		r = l;
    		while(r+1 <= n && a[r+1].y == a[r].y) ++r;
    		if(l-1 && a[l].y-a[l-1].y == ans) addedge(a[l-1].id,a[l].id);
    	}
    	FOR(i,1,n) sz[find(i)]++;
    	LL res = 0;
    	for(auto x:S) res += 1ll*sz[x.fi.fi]*sz[x.fi.se];
    	printf("%lld
    ",res);
    	return 0;
    }
    

    C

    可以发现这个操作本质是对 (1) 的移动。移动问题可以考虑维护处什么时候移动了。

    我们考虑对于每个 (1),求出它在哪些时间往前移动了一格,我们用一个 (01) 串表示,第 (i) 位是 (1) 表示在时间 (i) 这个 (1) 往前移动了一格。如果能对于每个 (1) 都求出来这个,就能求出每个 (1) 最后到了哪里。

    我们一开始是一个全 (0) 的串,考虑有了第 (i)(1) 的串,如何转移到第 (i+1)(1) 的串?如果下一个 (1) 和这个 (1) 是相邻的,可以发现如果在第 (i) 个位置的第 (j) 个时刻往前移动了一次,那么第 (i+1) 的第 (j+1) 个时刻也会往前移动,所以相当于是做一个右移操作,删除最后一个数字,并且在最前面加上一个 (0)

    如果和下一个 (1) 中间有 (c)(0),那么相当于还可以将这个序列的前 (c)(0) 变成 (1),相当于可以抵消 (c) 次上一个不动但是这一个动的情况。

    所以我们要支持如下操作:循环右移一位,将前 (c)(0) 变成 (1)。于是我们考虑直接维护 (0) 在序列中的位置,相当于要支持从后端删除,从前端插入,从前端删除,全局加,用双端队列+全局 tag 就可以解决了。

    如何算逆序对呢?我们考虑每次加入一个 (1) ,它对哪些轮有贡献:显然这个 (1) 会一直右移,移动到最后面出去就结束了,在它经过的所有时间答案都会 (+1),根据这个在序列中的位置可以算出多少时间后它会被弹掉,就能确定他贡献的一段区间,差分即可。

    这种题就是我完全想不到吧。。如果发现本质是对谁的移动就可以考虑什么时候移动

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int ha = 998244353;
    const int MAXN = 5e6 + 5;
    LL ans;
    int b=1;
    
    inline void add(int &x,int y){
    	x += y-ha;x += x>>31&ha;
    }
    
    inline void answer(int val){
    	ans ^= (1ll*val*b%ha);
    	b = 233ll*b%ha;
    }
    
    char str[MAXN];
    int n,T;
    std::vector<int> ps;
    bool vis[MAXN];
    int cf[MAXN];
    
    int main(){
    	scanf("%d",&T);scanf("%s",str+1);n = strlen(str+1);
    	FOR(i,1,n) if(str[i] == '1') ps.pb(i);
    	std::deque<int> q;int tag = 0;int las = 0;
    	FOR(i,1,T) q.pb(T-i);int rem = (int)ps.size();
    	for(auto x:ps){
    		int cur = x-las-1;--rem;
    		if(!q.empty() && q.back()+tag == 0) q.pop_back();
    		tag--;
    		q.push_front(T-1-tag);
    		while(!q.empty() && cur--){
    			int pos = T-(q.front()+tag);
    			cf[pos]++;cf[pos+rem+1]--;
    			q.pop_front();
    		}
    		vis[x-(T-(int)q.size())] = 1;
    		// if(x == 4){
    			// for(auto y:q) DEBUG(y+tag);
    		// }
    		las = x;
    	}
    	FOR(i,1,n) putchar(vis[i]+'0');puts("");
    	FOR(i,1,T) add(cf[i],cf[i-1]);
    	int sm = 0;
    	ROF(i,n,1){
    		if(str[i] == '0') sm++;
    		else add(cf[0],sm);
    	}
    	FOR(i,1,T) add(cf[i],cf[i-1]);
    	FOR(i,0,T) answer(cf[i]);
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    automaticallyAdjustsScrollViewInsets的作用
    UIView的一些常用属性和方法
    iOS sqlite 增删改查 简单封装(基于 FMDB)
    iOS 状态栏管理
    UINavigationController 总结
    storyboard 总结
    NSPredicate 谓词
    AFNetworking 简单应用
    iOS 网络编程
    归档 NSKeyedArchiver
  • 原文地址:https://www.cnblogs.com/rainair/p/14304986.html
Copyright © 2020-2023  润新知