Codeforces GYM101350 题解

91 阅读5分钟

比赛链接

A

思路

当中间点确定,左侧000...1000...1的长度与右侧1000...01000...0的长度乘积就会贡献一次答案,前提是左右两个11与中间点之间的11的个数相等。分别预处理出每个11左侧和右侧连续00的长度,考虑对每个长度计算贡献。
对于每个11,左侧紧挨的00与右侧紧挨的00单独计算,之后以第一个11为例,第一个11会分别与第3311及其后边紧挨的00贡献一次答案(当第2211为中间点时),与第5511贡献一次答案(当第3311为中间点时),以此类推。可以发现对于下标来说,都是奇数在一起贡献答案,偶数在一起贡献答案,因此可以维护两个后缀和来优化。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll> 
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
inline void In(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		scanf("%lld",va_arg(lis,ll*));
}
inline void Out(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		printf("%lld\n",va_arg(lis,ll));
}
inline void Out_(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		printf("%lld ",va_arg(lis,ll));
}
vector<ll> lft;
ll n;
string s;
void print()
{
	for(auto k:lft)
		cout<<k<<" ";
	puts("\n");
}
void solve()
{
	In(1,&n);
	cin>>s;
	s=' '+s;
	ll cnt=0,sum1=0,sum2=0,ans=0;
	lft.clear();
	rep(i,1,n)
	{
		if(s[i]=='0')
			++cnt;
		else
		{
			lft.emplace_back(cnt);
			if(lft.size()&1)
				sum1+=cnt+1;
			else
				sum2+=cnt+1;
			cnt=0;
		}
	}
	lft.emplace_back(cnt);
	if(lft.size()&1)
		sum1+=cnt+1;
	else
		sum2+=cnt+1;
	sum1-=lft[0]+1;
	rep(i,0,(int)lft.size()-2)
	{
		ans+=lft[i]*lft[i+1];
		if((i&1)==0)
		{
			sum2-=lft[i+1]+1;
			ans+=(lft[i]+1)*sum2;
		}
		else
		{
			sum1-=lft[i+1]+1;
			ans+=(lft[i]+1)*sum1;
		}
	}
	Out(1,ans);
}
int main()
{
	int _;
	cin>>_;
	while(_--)
	{
		solve();
	}
}

F

思路

每个猴子的最终状态由它听到的最后一个笑话决定,如果这个笑话听了恰好11次,它就在地上,否则就在座位上。
先求出每个猴子听到的最后一个笑话。利用差分,分别标记一下每个笑话的左右端点。
每次碰到一个左端点,就把这个笑话的标号(也就是被讲的次序)扔到优先队列中,并增加一次这类笑话的出现次数(注意这里“个”和“类”的区别),把这个笑话标记为入队,表示之后的猴子都会受到这个笑话的影响。因为要找听到的最后一个笑话,因此优先队列中的元素按照笑话的标号从大到小排序,越晚讲的笑话排序越靠前,这样队首元素就是当前猴子听到的最后一个笑话。
每次碰到右端点,就减少一次这类笑话的出现次数,并把它标记为不在队列中。如果在之后某个笑话出现在队首但是被标记为已经出队,就可以直接把它pop掉(毕竟不可以直接在priority_queue内部删除元素)。
确定每个猴子听到的最后一个笑话的同时,因为还维护了这类笑话的出现次数,所以可以直接根据这个出现次数维护答案。

另外一种思路是用线段树搞,对于每次讲笑话,区间修改一下这些猴子听到的笑话,这样最后的树存储的就是每个猴子听到的最后一个笑话。之后单独考虑每一类笑话,每次用一颗线段树维护下每个猴子听这类笑话的次数,如果这是这个猴子听的最后一个笑话且它听了00次或11次以上,就说明这个猴子在地上,反之就在座位上。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll> 
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
inline void In(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		scanf("%lld",va_arg(lis,ll*));
}
inline void Out(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		printf("%lld\n",va_arg(lis,ll));
}
inline void Out_(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		printf("%lld ",va_arg(lis,ll));
}
const ll N=1E5+10;
ll n,m;
ll ma[N],type[N],inq[N];
vector<ll> a[N];
priority_queue<ll> q;
void print()
{
	rep(i,1,n)
		cout<<ma[i]<<" ";
	cout<<endl;
}
void solve()	
{
	In(2,&n,&m);
	rep(i,1,n+1)
		a[i].clear();
	rep(i,1,m)
		inq[i]=0;
	while(!q.empty())
		q.pop();
	rep(i,1,m)
	{
		ll x,ty,len;
		In(3,&x,&ty,&len);
		ll l=max(1ll,x-len),r=min(n,x+len)+1;
		ma[ty]=0;
		type[i]=ty;
		a[l].emplace_back(i);
		a[r].emplace_back(-i);
	}
	ll ans=0;
	rep(i,1,n)
	{
		for(auto k:a[i])
		{
			if(k>0)
			{
				q.push(k);
				ma[type[k]]++;
				inq[k]=1;
			}
			else
			{
				ma[type[-k]]--;
				inq[-k]=0;
			}
		}
		while(!q.empty() && !inq[q.top()])
			q.pop();
		if(q.empty() || ma[type[q.top()]]!=1)
			++ans;
	}
	Out(1,ans);
}
int main()
{
	int _;
	cin>>_;
	while(_--)
	{
		solve();
	}
	
}

G

思路

对于每个四边形,当左上角坐标为(i,j)(i,j)时,右下角的坐标有(ni+1)×(mj+1)(n-i+1)\times (m-j+1)种选法(包括两个坐标重合的情况)。因此对于规模为n×mn\times m的四边形,小四边形的种数为Σi=1nΣj=1mi×j\Sigma_{i=1}^n\Sigma_{j=1}^mi\times j。根据等差数列求和公式化简一下可以O(1)O(1)得到答案。
用所有可能的情况减去不合法的情况就是答案。如果一个雷位于(i,j)(i,j),那么所有左上角在雷左上角,右下角在雷右下角的四边形都是不合法的(包括某个角与雷重合的情况)。显然只考虑这一个雷的时候不合法的情况有i×j×(ni+1)×(mj+1)i\times j\times (n-i+1)\times (m-j+1)种。直接减去即可。
当有多个雷的时候,这样减会有重复的,因此用容斥原理。
雷的个数只有2020,二进制枚举每一种组合,如果这个组合中雷的个数为奇数,就从答案中减去他们的并集,否则就加上。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll> 
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
inline void In(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		scanf("%lld",va_arg(lis,ll*));
}
inline void Out(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		printf("%lld\n",va_arg(lis,ll));
}
inline void Out_(ll _,...)
{
	va_list lis;
	va_start(lis,_);
	while(_--)
		printf("%lld ",va_arg(lis,ll));
}
const ll N=25;
ll n,m,k;
ll x[N],y[N];
ll check(ll sta)
{
	ll cnt=0,ret=0;
	ll lx=LLM,ly=LLM,rx=0,ry=0;
	vector<pii> a;
	rep(i,0,k-1)
	{
		if((1<<i)&sta)
		{
			++cnt;
			lx=min(lx,x[i+1]);
			rx=max(rx,x[i+1]);
			ly=min(ly,y[i+1]);
			ry=max(ry,y[i+1]);
		}
	}
	ret=lx*ly*(n-rx+1)*(m-ry+1);
	return ret*(cnt&1?1:-1);
}
void solve()
{
	cin>>n>>m>>k;
	ll ans=0;
	rep(i,1,k)
	{
		cin>>x[i]>>y[i];
	}
	ll sum=m*(m+1)/2;
	ans=n*sum+n*(n-1)/2*sum;
	
	ll lim=1<<k;
	rep(i,1,lim-1)
	{
		ans-=check(i);
	}
	
	Out(1,ans);
	
}
int main()
{
	int _;
	cin>>_;
	while(_--)
	{
		solve();
	}
}