good problem-7
1.Ex - Yet Another Path Counting
题意:
\(N*N\)的棋盘,每个格子有一个颜色标号\((1\leq a_{i,j} \leq N*N)\)
两个有相同颜色棋格,它们的路径方案数为从其中一个棋格只能向下或向右移动到另一个棋格的方案数
问任何两个有相同颜色棋格的路径方案数的总和
题解:
首先暴力做法有2个:
- 枚举起点和终点,两点之间的路径方案数组合数可以求出,时间复杂度\(O(N^4)\)
- 对每个种颜色,进行dp
假设当前枚举的颜色为k, 设\(dp_{i,j}\),以当前颜色k的格子为起点到终点(i,j)的方案数
\(dp_{i,j}=dp_{i-1,j}+dp_{i,j-1}+[a[i][j]==k]\)
时间复杂度为\(O(N^4)\)
考虑这道题怎么做?考虑颜色
虽然上述暴力的两种做法都是\(O(N^4)\),但是因为题目有颜色的限制条件,对于第一种适合某种颜色的格子数较少的情况,第二种适合某种颜色的格子数较多的情况
那么可以考虑分类使用2种方法
1.若有的\(颜色的格子数\)大于N,那么所有这种情况的\(颜色\)最多N个
那么对于所有颜色的格子数大于N的颜色进行dp
时间复杂度为\(O(N^3)\)
2.若有的\(颜色的格子数n_i\)小于N,考虑第一种暴力做法
那么时间复杂度为\(O(\sum (n_i)^2)\leq O(N\sum n_i)\leq O(N^3)\)
因此总的时间复杂度\(O(N^3)\)
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define pb push_back
#define mp make_pair
#define se second
#define fi first
using namespace std;
const int maxn=3e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double eps=1e-9;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
ll power(ll x,ll y){
ll ans=1;
while(y){
if(y&1)ans=ans*x%MOD;
y>>=1;x=x*x%MOD;
}
return ans;
}
ll inv[maxn],fac[maxn],dp[402][402];
ll C(ll n,ll m){
if(m==0 || n==m)return 1ll;
ll ans=fac[n]*inv[m]%MOD;
return ans*inv[n-m]%MOD;
}
int n,a[401][401];
int main(){
n=read();vector<pa>mm[n*n+1];
fac[0]=1;for(ll i=1;i<=2*n;i++)fac[i]=fac[i-1]*i%MOD;
inv[2*n]=power(fac[2*n],MOD-2);
for(ll i=2*n-1;i;i--)inv[i]=inv[i+1]*(i+1)%MOD;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
a[i][j]=read();
mm[a[i][j]].pb(mp(i,j));
}
ll ans=0;
for(int k=1;k<=n*n;k++){
if(mm[k].size()>n){
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
dp[i][j]=0;
if(i>1)(dp[i][j]+=dp[i-1][j])%=MOD;
if(j>1)(dp[i][j]+=dp[i][j-1])%=MOD;
if(a[i][j]==k){
(dp[i][j]+=1)%=MOD;
(ans+=dp[i][j])%=MOD;
}
}
}
else {
for(int i=0;i<mm[k].size();i++)for(int j=i;j<mm[k].size();j++){
if(mm[k][i].se<=mm[k][j].se){
ans+=C(mm[k][j].fi+mm[k][j].se-mm[k][i].fi-mm[k][i].se,
mm[k][j].fi-mm[k][i].fi);
ans%=MOD;
}
}
}
}
printf("%lld\n",(ans%MOD+MOD)%MOD);
return 0;
}
2.E. Mark and Professor Koro
题解
我们可以用二进制加法来模拟。用线段树来维护。
处理减法,线段树上二分找到一个为1 的点
那么答案就是找到二进制中的最高位,我们也可以使用二分。
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#define ll long long
#define pa pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
const int maxn=2e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
struct Segtree{
int tr[maxn<<3],lz[maxn<<3];
void add(int k,int l,int r,int val){
lz[k]+=val;
tr[k]+=(r-l+1)*val;
return;
}
void pushdown(int k,int l,int r){
if(!lz[k])return ;
int mid=(l+r)>>1;
add(k<<1,l,mid,lz[k]);add(k<<1|1,mid+1,r,lz[k]);
lz[k]=0;return ;
}
void change(int k,int l,int r,int L,int R,int val){
if(L>R)return ;
pushdown(k,l,r);
if(r<L || l>R)return ;
if(L<=l && r<=R){
add(k,l,r,val);//将区间全设为val
return ;
}
int mid=(l+r)>>1;
change(k<<1,l,mid,L,R,val);change(k<<1|1,mid+1,r,L,R,val);
tr[k]=tr[k<<1]+tr[k<<1|1];
return ;
}
int find0(int k,int l,int r,int L,int R){
//找到区间中第一个为0的点位置
if(l==r)return (tr[k]==1)?-1:l;
pushdown(k,l,r);
int mid=(l+r)>>1;
if(L<=l && r<=R){
if(tr[k]==r-l+1)return -1;
int now=find0(k<<1,l,mid,L,R);
return (now==-1)?find0(k<<1|1,mid+1,r,L,R):now;
}
if(R<=mid)return find0(k<<1,l,mid,L,R);
else if(mid<L)return find0(k<<1|1,mid+1,r,L,R);
int now=find0(k<<1,l,mid,L,R);
return (now==-1)?find0(k<<1|1,mid+1,r,L,R):now;
}
int find1(int k,int l,int r,int L,int R){
//找到区间中第一个为1的点位置
if(l==r)return (tr[k]==0)?-1:l;
pushdown(k,l,r);
int mid=(l+r)>>1;
if(L<=l && r<=R){
if(tr[k]==0)return -1;
int now=find1(k<<1,l,mid,L,R);
return (now==-1)?find1(k<<1|1,mid+1,r,L,R):now;
}
if(R<=mid)return find1(k<<1,l,mid,L,R);
else if(mid<L)return find1(k<<1|1,mid+1,r,L,R);
int now=find1(k<<1,l,mid,L,R);
return (now==-1)?find1(k<<1|1,mid+1,r,L,R):now;
}
int getans(int k,int l,int r){
//找到二进制最高位
pushdown(k,l,r);
if(l==r)return l;
int mid=(l+r)>>1;
if(tr[k<<1|1])return getans(k<<1|1,mid+1,r);
return getans(k<<1,l,mid);
}
}T;
int n,q,a[maxn];
void solve(int x,int val){
//x位置加val
if(val==1){
int pos=T.find0(1,1,maxn,x,maxn);
T.change(1,1,maxn,pos,pos,1);
T.change(1,1,maxn,x,pos-1,-1);
}
else {
int pos=T.find1(1,1,maxn,x,maxn);
T.change(1,1,maxn,pos,pos,-1);
T.change(1,1,maxn,x,pos-1,1);
}
return ;
}
int main(){
n=read();q=read();
for(int i=1;i<=n;i++){
a[i]=read();
solve(a[i],1);
}
while(q--){
int pos=read(),val=read();
solve(a[pos],-1);
a[pos]=val;
solve(a[pos],1);
printf("%d\n",T.getans(1,1,maxn));
}
return 0;
}
3.F - Find 4-cycle
题意:有两个集合S,T;集合内部的点没有边相连,输出一个包含四个点的环
题解:
对于\(x,y\in T\),若存在\(z,w\in S,\)且z和w都与x和y有边相连,那么这四个点是一个环
考虑如何寻找
因为T集合个数不超过3000,
我们枚举S集合中的点i,枚举与i相连的两点x和y\((x,y\in T, x\not =y)\)
若book[x][y]=0,令book[x][y]=i
否则输出book[x][y],x,y,i
时间复杂度为\(O(|T|^2)\)
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
const int maxn=7e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int s,t,m;
int book[3001][3001];
int main(){
s=read(),t=read(),m=read();
vector<vector<int> >L(s+2);
for(int i=1;i<=m;i++){
int x=read(),y=read();
if(x>y)swap(x,y);
L[x].pb(y);
}
for(int i=1;i<=s;i++){
for(auto x:L[i])for(auto y:L[i]){
if(x==y)continue;
if(book[x-s][y-s]==0)book[x-s][y-s]=i;
else {
cout<<x<<" "<<y<<" "<<book[x-s][y-s]<<" "<<i<<endl;
return 0;
}
}
}
puts("-1");
return 0;
}
4.E - At Least One
题意:对于每个\(i\in1~m\),问有多少个长度为i区间包含\(a_j或b_j(对于每个1\leq j\leq n)\)
题解:
不难知道\(x\leq i \leq j \leq y\),若[i,j]是合法的那么[x,y]也是合法的
那么我们考虑滑动窗口做法
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
const int maxn=7e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m;
int ans[maxn];
int main(){
n=read();m=read();
vector<vector<int> >v(m+1);
map<int,int>mm; // 第i个区间是否被包含
for(int i=1;i<=n;i++){
int a=read(),b=read();
v[a].pb(i);v[b].pb(i);
}
int l=1,r=1,cnt=n;
while(l<=m){
while(r<=m && cnt!=0){
for(auto i:v[r]){
if(mm[i]==0)cnt--;
mm[i]++;
}
r++;
}
if(cnt!=0)break;
ans[r-l]++; //[l,r-1]是找到的区间,长度为r-l
ans[m-l+2]--;
//长度为[r-l,m-l+2]内的答案+1
//ans为差分数组
for(auto i:v[l]){
mm[i]--;
if(mm[i]==0)cnt++;
}
l++;
}
for(int i=1;i<=m;i++){
ans[i]+=ans[i-1];
printf("%d ",ans[i]);
}
return 0;
}
5.C. Recover an RBS
题意:给定一个字符串由左括号右括号和问号组成,?可以填任意括号,请问能通过在?处填入括号使得确定一个唯一的合法括号序列?
什么是合法的括号序列? 在括号序列的任意前缀中必须要保证左括号的数量>=右括号的数量。
什么时候不唯一? 其实括号序列不唯一的本质是存在可以交换的左括号和右括号。
因此我们贪心的考虑。假设我们已经有了l个左括号和r个右括号,左括号越靠前一定能构造出合法序列。
那么我们把n/2-l个左括号依次填入?中,剩下的问号就填右括号。
这一定是一个合法解,但是我们需要找出哪两个括号可能能进行交换。
我们记录手动放入最后一个左括号和第一个手动放入的右括号。
我们贪心的考虑,这是距离最近的两个括号,所以这两个括号是最有可能被交换的。
为什么呢?根据之前提到的性质,括号序列的任意前缀中必须要保证左括号的数量>=右括号的数量。
我们要让左括号尽可能的靠前,右括号靠后,因此找到距离最后一个左括号最近的手动放入的右括号就是最优条件。
如何快速判断合法?左括号取1,右括号取-1,那么这个序列的前缀和必须大于等于0才合法
交换一对括号相当于这段区间的值-2,因此我们只需要判断这段区间(区间指的是手动放入最后一个左括号和第一个手动放入的右括号)内是否<2即可表示存在唯一解。
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=1e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
void solve(){
string s;cin>>s;
int c1=0,c2=0,c3=0;
for(int i=0;i<s.length();i++){
if(s[i]=='(')c1++;
if(s[i]==')')c2++;
if(s[i]=='?')c3++;
}
if(c3<=1 || abs(c1-c2)==c3)YES;
int lr=-1,rc=inf;
for(int i=0;i<s.length();i++){
if(s[i]!='?')continue;
if(c1<s.length()/2)s[i]='(',c1++,lr=i;
else s[i]=')',c2++,rc=min(rc,i);
}
int sum=0;
for(int i=0;i<s.length();i++){
sum+=(s[i]=='('?1:-1);
if(i>=lr && i<rc && sum<2)YES;
}
NO;
}
int main(){
int t=read();
while(t--)solve();
return 0;
}
6.G. Trader Problem
启发式合并好题
题解
7.Serval and Essay
题意:对于某个点,如果它的所有入边的起点都是黑色的,那么它也是黑色的。最开始可以任意指定一个点是黑色,求图中最多黑点个数
首先可以发现当一个点u只有一个入度v的话,显然往v染色更优
那么我们可以将u和v缩成一个点,重新构图
重复上述操作,得到的图中点的size最大的size就是答案
用启发式合并即可
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
set<int>in[maxn],out[maxn];
int n,f[maxn],sz[maxn];
int find(int x){
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
void merge(int x,int y){
queue<pa>q;
q.push(mp(x,y));
while(!q.empty()){
auto z=q.front();q.pop();
int u=z.fi,v=z.se;
u=find(u),v=find(v);
if(u==v)continue;
if(out[u].size()>out[v].size())swap(u,v);
f[u]=v;sz[v]+=sz[u];
//v 大
for(auto i:out[u]){
out[v].insert(i);
in[i].insert(v);
in[i].erase(u);
if(in[i].size()==1)q.push(mp(i,v));
}
}
return ;
}
int main(){
int t=read();
for(int i=1;i<=t;i++){
n=read();
for(int i=1;i<=n;i++){
int k=read();f[i]=i;sz[i]=1;
for(int j=1;j<=k;j++){
int x=read();
in[i].insert(x);
out[x].insert(i);
}
}
for(int i=1;i<=n;i++){
if(in[i].size()==1)merge((*in[i].begin()),i);
}
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,sz[i]);
printf("Case #%d: %d\n",i,ans);
for(int i=1;i<=n;i++)in[i].clear(),out[i].clear();
}
return 0;
}
8.F - Sorting Color Balls
若没有颜色,那么答案就是冒泡排序要进行的操作次数,也就是逆序对个数
因为有颜色,我们可以把相同颜色的数放在一起来求逆序对,最后总逆序对减去所有颜色分别的逆序对就是答案
9.B. Party
题意是:有n个人,m对朋友关系,不邀请第i个人会产生\(a_i\)的不高兴值,问你邀请n中的一些人,使得不高兴值最小且他们当中朋友关系的个数是偶数
首先m是偶数时,答案就是0,因为都可以邀请
若m是奇数
1.我们考虑删掉一个人,这个人有奇数个朋友关系
2.删掉2个人,这两个人是朋友关系,且他俩的好友个数(不包含他们自己的朋友关系)是奇数
不难发现这两种情况必然是会存在的,假如第一种情况没有,那么第二种情况必有
对这两种情况贪心选择
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e5+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m,a[maxn];
struct wzq{
int x,y;
};
int main(){
int t=read();
while(t--){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
map<int,int>mm;
vector<wzq>q(m);
for(int i=0;i<m;i++){
int x=read(),y=read();
q[i].x=x;q[i].y=y;
mm[x]++;mm[y]++;
}
if(m%2==0){puts("0");continue;}
else {
int minn=inf;
for(int i=1;i<=n;i++){
if(mm[i]&1)minn=min(minn,a[i]);
}
for(int i=0;i<m;i++){
int x=q[i].x,y=q[i].y;
if((mm[x]+mm[y]-1)&1)minn=min(minn,a[x]+a[y]);
}
cout<<minn<<endl;
}
}
return 0;
}
10.简单瞎搞题
普通dp为
\(dp_{i,j}\)为是否存在前i个数的平方相加为j的方案
\(dp_{i,j}=[ dp_{i-1,j-x_i*x_i} ],(l_i \leq x_i \r_i}\)
显然这是\(O(n^5)\)的
其实可以发现,这个dp数组可以是个bool类型的
考虑用bitset优化,bitset的时间复杂度为\(O(\frac{n}{32})(n为位数)\),但往往比这快
在这道题刚好可以通过
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e5+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n;
int main(){
n=read();
bitset<1000001>f[101];
f[0][0]=1;
for(int i=1;i<=n;i++){
int l=read(),r=read();
for(int j=l;j<=r;j++)f[i]|=f[i-1]<<(j*j);
}
cout<<f[n].count()<<endl;
return 0;
}