• AtCoder Regular Contest 120


    B.Uniformly Distributed

    题目描述

    点此看题

    解法

    首先可以观察出必要条件,也就是对于所有 ((i,j)) 要求 ((i+1,j))((i,j+1)) 的颜色相等,这样才能保证无论用什么方法走到 ((i+1,j+1)) 经过红色格子的数量都是一样的。

    这也是充分条件,因为任意时候路径经过红色格子数都是全相等的。

    所以对于每一个 (i+j) 统计颜色分布,如果全是.答案乘 (2),如果既有B又有W答案为 (0),否则答案不变。

    #include <cstdio>
    const int M = 505;
    const int MOD = 998244353;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,ans=1;char s[M][M];
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;i++)
    		scanf("%s",s[i]+1);
    	for(int i=2;i<=n+m;i++)
    	{
    		int c1=0,c2=0;
    		for(int j=1;j<=n;j++)
    		{
    			if(i-j<=0 || i-j>m) continue;
    			if(s[j][i-j]=='R') c1++;
    			if(s[j][i-j]=='B') c2++;
    		}
    		if(c1 && c2) ans=0;
    		if(!c1 && !c2) ans=2*ans%MOD;
    	}
    	printf("%d
    ",ans);
    }
    

    C.Swaps 2

    题目描述

    点此看题

    有长度为 (n) 的两个序列 (a,b),要求通过最少的操作数把 (a) 变成 (b)

    操作定义如下:选择 (1leq i<n),交换 (a_i,a_{i+1}),把 (a_i) 加上 (1)(a_{i+1}) 减去 (1)

    (2leq nleq 2cdot 10^5,0leq a_i,b_ileq 10^9)

    解法

    首先观察:无论怎么操作数列的总和是不会变的,一次操作相当于把某个数左移,并给它加 (1)

    要从变化中找不变量,我们对于每个数可以定义一个势能 (a_i+i),势能相等的两个位置才可能匹配。

    然后依次考虑 (b) 的每一个位置,找到初始位置最小并且还未匹配的 (a),由于匹配会带来他前面数位置的移动,所以用一个树状数组维护每个数已经被迫移动的距离即可,时间复杂度 (O(nlog n))

    #include <cstdio>
    #include <iostream>
    #include <set>
    using namespace std;
    #define pii pair<int,int>
    #define make make_pair 
    #define int long long
    const int M = 200005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,ans,bit[M];set<pii> s;
    int lowbit(int x)
    {
    	return x&(-x);
    }
    void add(int x,int f)
    {
    	for(int i=x;i<=n;i+=lowbit(i))
    		bit[i]+=f;
    }
    int ask(int x)
    {
    	int r=0;
    	for(int i=x;i>0;i-=lowbit(i))
    		r+=bit[i];
    	return r;
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    		s.insert(make(read()+i,i));
    	for(int i=1;i<=n;i++)
    	{
    		int x=read();
    		set<pii>::iterator it=s.lower_bound(make(x+i,0));
    		if(it==s.end() || (*it).first!=x+i)
    		{
    			puts("-1");
    			return 0;
    		}
    		int t=ask((*it).second);
    		add(1,1);add((*it).second,-1);
    		ans+=(*it).second+t-i;
    		s.erase(it);
    	}
    	printf("%lld
    ",ans); 
    }
    

    D.Bracket Score 2

    题目描述

    对于一个长为 (2n) 的括号序列 (s),如果对于 (i<j)(s_i=)((,s_j=)),并且 (i,j) 中间的括号序列恰好能匹配,那么可以产生 (|a_i-a_j|) 的贡献。

    给定 (n,a),试构造 (s) 使得贡献和最大。

    (1leq nleq 2cdot 10^5,1leq a_ileq 10^9)

    解法

    先考虑贡献产生的条件,对于最两边的元素只有 (()()) 才能产生贡献而 ()() 则不行,所以右括号只能和它匹配的左括号产生贡献,贡献的对数最多是 (n)

    现在考虑最大化答案,(dp) 是不容易的,我们考虑构造答案上界。由于是绝对值相减的形式那么一定有 (n) 个数字前面符号是正,(n) 个数字前面符号是负,那么我们让最大的 (n) 个符号为正,其它符号为负。

    也就是我们要让最大的 (n) 个数和最小的 (n) 个数匹配,那么线性扫一遍不难构造。

    #include <cstdio>
    #include <algorithm>
    #include <stack>
    using namespace std;
    const int M = 400005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,a[M],c[M];char t[M];stack<int> s[2];
    struct node
    {
    	int x,id;
    	bool operator < (const node &r) const
    	{
    		return x<r.x;
    	}
    }b[M];
    signed main()
    {
    	n=2*read();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		b[i]=node{a[i],i};
    	}
    	sort(b+1,b+1+n);
    	for(int i=1;i<=n/2;i++)
    		c[b[i].id]=1;
    	for(int i=1;i<=n;i++)
    	{
    		if(!s[c[i]^1].empty())
    		{
    			t[s[c[i]^1].top()]='(';
    			t[i]=')';s[c[i]^1].pop();
    		}
    		else
    			s[c[i]].push(i);
    	}
    	printf("%s
    ",t+1);
    }
    

    E.1D Party

    题目描述

    (n) 个人,一开始第 (i) 个人在 (a_i) 处,每秒每个人可以移动一格,或者不动,问所有 (1leq i<n) 满足 (i)(i+1) 相遇过的最小时间。

    (2leq nleq 2cdot 10^5,0leq a_ileq 10^9)(a_i) 全为偶数并且以升序给出

    解法

    首先可以有如下观察:每个人时时刻刻都是在动的,(i) 一开始会一直往右边走直接碰到 (i+1),然后一直往左走,反之亦然。

    然后我们考虑如果 (i)(i+1) 是相向而行的,他们碰面了之后不反向,而是照着原来的方向继续行进,只是他们需要交换各自的任务。

    img

    如上图,我们可以转化题意:横轴代表现在每个人的坐标,纵轴代表时间。那么我们钦定两个人同向走就能让中间的点都能满足条件(注意端点还是不满足条件的),我们称这个过程为配对,配对的过程中有如下限制:

    • 每个点都必须被包含在配对点连成的三角形中。
    • 每个点至多被配对一次。
    • 相邻两个配对三角形直接必须有交。

    那么可以用 (dp) 来配对,设 (f[i]) 表示(i) 个点均合法的方案数,转移我们让 (i+1)(j-1) 配对就可以得到区间 ([j,i]) 的合法,但是注意 (i=j) 的转移会出问题,因为连续四个点让它们两两配对会导致不满足性质三,所以转移如下:

    [f[i]leftarrowmax(f[j-1],a[i+1]-a[j-1])and jin[1,i-1] ]

    看上去是 (O(n^2)) 的,其实 (j) 的取值范围很有限因为根本不可能配对得那么远,所以循环到 (j=i-3) 即可。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 200005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,a[M],f[M];
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    		a[i]=read();
    	a[0]=a[1];a[n+1]=a[n];
    	for(int i=2;i<=n;i++)
    	{
    		f[i]=a[i+1]-a[1];
    		for(int j=max(i-3,1);j<=i-1;j++)
    			f[i]=min(f[i],max(f[j-1],a[i+1]-a[j-1]));
    	}
    	printf("%d
    ",f[n]/2);
    }
    

    F.Wine Thief

    题目描述

    点此看题

    (n) 个钻石排成一排,第 (i) 个钻石的价值是 (a_i),阿七要拿走 (k) 个钻石来救大保,但是如果相邻两个钻石都被拿走神医就会发现。在不让神医发现的情况下,阿七想知道每种情况拿走钻石的权值和 的权值和是多少,答案取模 (998244353)

    (nleq 3cdot 10^5)

    解法

    直接 (dp) 是不行的,因为 (k) 的限制会升维。

    考虑全部方案数我们是会算的,用拆贡献的方法我们可以知道只要算出有多少种方案拿走钻石 (i) 即可,也就是我们要算一个单点的方案数。可以自然想到接环:把序列模型转化为等概率环模型

    (f(n,k)) 表示 (n) 个点的环拿走 (k) 个钻石的方案数,(g(n,k)) 表示 (n) 个点的序列拿走 (k) 个钻石的方案数,我们可以先钦定 (k) 个被拿走的钻石,再把 (k-1) 个插到空隙中保证合法,剩下的任意插空就行了,所以 (g(n,k)={n-k+1choose k})

    对于环我们讨论第一个钻石的情况,如果第一个钻石被拿走那么方案数是 (g(n-3,k-1)),如果不被拿走那么方案数是 (g(n-1,k)),所以 (f(n,k)=g(n-3,k-1)+g(n-1,k))

    那么在环中单点被拿走的方案数是 (frac{f(n,k)cdot k}{n}),但是我们要算的是序列的方案数,所以要算少的方案是第一个钻石和最后一个钻石同时选的方案,即 (g(n-4,k-2)),由于要算这个方案对中间数单点的贡献,我们令 (nleftarrow n-4,kleftarrow k-2) 作为子问题递归下去即可。

    注意的上面的讨论在 (nleq 3) 的情况下是不适用的,所以暴力特判即可。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 500005;
    const int MOD = 998244353;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,k,d,ans,a[M],b[M],f[M],fac[M],inv[M];
    void init(int n)
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=2;i<=n;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    }
    int C(int n,int m)
    {
    	if(n<m) return 0;
    	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    int g(int n,int k)
    {
    	if(n<0 || k<0) return 0;
    	return C(n-k+1,k);
    }
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    void solve(int n,int m,int z)
    {
    	if(m<=0) return ;
    	if(n<=3)
    	{
    		if(n==1 && m==1) f[z]=1;
    		if(n==2 && m==1) f[z]=f[z+1]=1;
    		if(n==3 && m==1) f[z]=f[z+1]=f[z+2]=1;
    		if(n==3 && m==2) f[z]=f[z+2]=1;
    		return ;
    	}
    	b[z-1]=(g(n-3,m-1)+g(n-1,m))*m%MOD*qkpow(n,MOD-2)%MOD;
    	f[z]=f[n+z-1]=g(n-4,m-2);
    	solve(n-4,m-2,z+2);
    }
    signed main()
    {
    	init(5e5);
    	n=read();k=read();d=read();
    	for(int i=1;i<=n;i++) a[i]=read();
    	solve(n,k,1);
    	for(int i=1;i<=n;i++)
    		b[i]=(b[i]+b[i-1])%MOD;
    	for(int i=1;i<=n;i++)
    		ans=(ans+a[i]*(f[i]+b[min(i-1,n-i)]))%MOD;
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    Review Python装饰器
    Python自动化开发三元运算 列表解析 生成器表达式
    Python自动化开发函数02
    Python自动化开发函数03
    Python自动化开发文件
    ELK02ELK收集Linux系统平台应用系统日志
    ELK01Elasticsearch
    html5调用摄像头并拍照
    Docker 安装 PostgreSQL
    《TensorFlow+Keras自然语言处理实战》图书介绍
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14825837.html
Copyright © 2020-2023  润新知