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