[CQOI2012]局部极小值 & Mike and Foam

[CQOI2012]局部极小值

题意:
有一个 n n n m m m列的整数矩阵,其中 1 1 1 n ∗ m n*m nm之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。
( n < = 4 , m < = 7 ) (n<=4,m<=7) (n<=4,m<=7)

思路:
对于确定极小值点的方案,可以通过 d p dp dp来解决, d p [ i ] [ j ] dp[i][j] dp[i][j],代表当前枚举到第几个数,哪些极小值点被赋值了,满足极小值的定义,即极小值周围的点必须比极小值点后赋值(从小到大枚举数字赋值的话),按照这个思路去转移即可。
满足了这些点是极小值,但是不能满足其他点不是极小值,用容斥来解决,考虑枚举所有的取极小值的点的方案,然后做一次 d p dp dp即可;
枚举去点方案时,可以用两两极小值点不相邻取剪枝,方案不是特别多;
代码:

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
#define ll long long 
#define mp make_pair
const ll mod=12345678;

int n,m;
char s[5][10];
ll ans=0;
vector<pair<int,int> >v;
pair<int,int>st[30];
int top=0;
int d[][2]={1,0,-1,0,0,1,0,-1,1,1,1,-1,-1,1,-1,-1};
ll dp[30][270];
int cnt[270];
int mpp[5][10];
ll solve(){
	memset(dp,0,sizeof(dp));
	memset(cnt,0,sizeof cnt);
	dp[0][(1<<top)-1]=1;
	for(int i=0;i<(1<<top);i++){
		memset(mpp,0,sizeof mpp);
		for(int j=0;j<top;j++){
			mpp[st[j+1].first][st[j+1].second]=1;
			if((1<<j)&i){
				for(int k=0;k<8;k++){
					int nx=st[j+1].first+d[k][0];
					int ny=st[j+1].second+d[k][1];
					if(nx<1||nx>n||ny<1||ny>m)continue;
					mpp[nx][ny]=1;
				}
			}
		}
		for(int j=1;j<=n;j++){
			for(int k=1;k<=m;k++){
				if(mpp[j][k]==0)cnt[i]++;
			}
		}
	}
	for(int i=0;i<n*m;i++){
		for(int j=0;j<(1<<top);j++){
			if(dp[i][j]==0)continue;
			int res=cnt[j]-i;
			for(int k=0;k<top;k++){
				if((1<<k)&j){
					dp[i+1][j^(1<<k)]+=dp[i][j];
					dp[i+1][j^(1<<k)]%=mod;
				}else res++;
			}
			dp[i+1][j]+=dp[i][j]*res%mod;
			dp[i+1][j]%=mod;
		}
	}
	return dp[n*m][0];
}
void dfs(int x,int cnt){
	if(x==v.size()){
		if(cnt%2==1)ans-=solve();
		else ans+=solve(); 
		ans=(ans+mod)%mod;
		return ;
	}
	dfs(x+1,cnt);
	int f=1;
	for(int i=0;i<8;i++){
		int nx=v[x].first+d[i][0];
		int ny=v[x].second+d[i][1];
		if(nx<1||nx>n||ny<1||ny>m)continue;
		if(s[nx][ny]=='X')f=0;
	}
	if(f){
		s[v[x].first][v[x].second]='X';
		top++;
		st[top]=mp(v[x].first,v[x].second);
		dfs(x+1,cnt+1);
		top--;
		s[v[x].first][v[x].second]='.';
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			if(s[i][j]=='X'){
				top++;st[top]=mp(i,j);
			}else {
				int f=1;
				for(int k=0;k<8;k++){
					int nx=i+d[k][0];
					int ny=j+d[k][1];
					if(nx<1||nx>n||ny<1||ny>m)continue;
					if(s[nx][ny]=='X')f=0;
				}
				if(f)v.push_back(mp(i,j));
			}
		}
	}
	dfs(0,0);
	printf("%lld\n",ans);
	return 0;
}

Mike and Foam

题意:
M i k e Mike Mike R i c o Rico Rico酒吧的调酒师。在 R i c o Rico Rico酒吧,他们将啤酒杯放在一个特殊的架子上。在 R i c o Rico Rico酒吧,有 n n n种啤酒编号从 1 1 1 n n n。第 i i i瓶啤酒上面有 a i a_{i} ai毫升的泡沫。
M a x i m Maxim Maxim M i k e Mike Mike的老板。今天他让 M i k e Mike Mike回答 q q q个查询。最初架子是空的。在每个操作中, M a x i m Maxim Maxim给他一个编号 X X X。如果编号为 X X X的啤酒已经在架子上,那么 M i k e Mike Mike应该从架子上取下它,否则他应该把它放在架子上。

每次询问后, M i k e Mike Mike应该告诉他架子的分数。他们认为货架的分数是满足 i < j i<j i<j并且 g c d ( a i , a j ) = 1 gcd(a_{i},a_{j})=1 gcd(ai,aj)=1的数对 ( i , j ) (i,j) (i,j)的个数。
n , q < = 2 e 5 , a i < = 5 e 5 n,q<=2e5,a_i<=5e5 n,q<=2e5,ai<=5e5

思路:
两个数互质 == 两个数没有相同的质因子;
不难发现 a i < = 5 e 5 a_i<=5e5 ai<=5e5,意味着 a i a_i ai的不同质因子不会超过7个;
那么根据质因子去容斥即可得到和当前数互质的数的个数;
预处理出每个数的质因子,再用 u n o r d e r e d unordered unordered_ m a p map map去存每个容斥项即可。

代码:

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
#define ll long long 

unordered_map<ll,int>mp;
const ll mod=1e9+7;
int n,m;
struct node{
	ll h;
	int sig;
};
vector<node>v[200050];
int vis[200050];
int num=0;
ll ans=0;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		int a;scanf("%d",&a);
		vector<int>tmp;
		tmp.clear();
		for(int j=2;1LL*j*j<=a;j++){
			if(a%j==0){
				tmp.push_back(j);
				while(a%j==0)a/=j;
			}
		}
		if(a!=1)tmp.push_back(a);
		for(int j=0;j<(1<<tmp.size());j++){
			ll h=1;
			int si=1;
			for(int k=0;k<tmp.size();k++){
				if((1<<k)&j){
					h=h*977777+tmp[k];
					h%=mod;
					si=-si; 
				}
			}
			v[i].push_back(node{h,si});
		}
	}
	while(m--){
		int a;
		scanf("%d",&a);
		ll res=0;
		res=num;
		int f=vis[a];
		for(auto to:v[a]){
			if(f){
				mp[to.h]--;
				res+=to.sig*mp[to.h];
			}else{
				res+=to.sig*mp[to.h];
				mp[to.h]++;
			}
		}
		vis[a]^=1;
		if(f)ans-=res;
		else ans+=res;
		printf("%lld\n",ans);	
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值