6.1模拟赛
第一题:Chess
题目大意:
一个 n×n 的棋盘,上面有若干障碍,剩下的是空格。你可以在空格中放棋子,两个棋子能互相攻击当且仅当他们位于同一行或者同一列并且之间没有障碍(中间有别的棋子也是可以的)。q个询问:假如在这个棋盘上放k个棋子,至少产生多少对互相攻击的棋子?
数据范围:
n<=50,q<=10000
题解:
40分:
贪心。每次选形成新的攻击最小的格子,一样再选影响最小的格子。
100分:
竟然是费用流。。。好吧。。。
把行和列上连续的一段空格看成点,
每个格点对应的行和列的点之间连边,cap=1, cost=0
S向每个行点连x条边,x是这个点对应的空格的长度,每条边cap=1, cost=0 1 2……k-1
同理,每个列点像T连边。
这样连重边保证了当这行/列已经有几个棋子了,下一个棋子的花费就是几。
最后建立超级源SS,像S连cap=k, cost=0。k就是询问的棋子个数。
跑一边费用流就是答案。
注意询问特别多,可以把询问排序去重,这样每次不用重新构图,直接修改SS->S的cap,利用上次的流量继续跑即可。
Code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 10005;
const int M = 50005;
int n,g[105][105],hang[105][105],lie[105][105],len[N],SS,S,T,cnt,pt,ans[N],que[N],tp[N],totcost;
struct Edge{
int from,to,next,cap,flow,cost;
}e[M*2];
int head[N], ec=1;
void clear(){ memset(head,0,sizeof(head)); ec=1; }
void add(int a,int b,int cap,int cost){
ec++; e[ec].to=b; e[ec].from=a;
e[ec].cap=cap; e[ec].flow=0; e[ec].cost=cost;
e[ec].next=head[a]; head[a]=ec;
}
void add2(int a,int b,int cap,int cost){
add(a,b,cap,cost); add(b,a,0,-cost);
}
bool vis[N]; int d[N]; int pre[N];
bool spfa(){
memset(vis,false,sizeof(vis));
memset(d,0x3f,sizeof(d));
queue<int> q; q.push(SS); d[SS]=0; vis[SS]=true;
while(!q.empty()){
int u=q.front(); q.pop(); vis[u]=false;
// D(u); E;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(d[v]>d[u]+e[i].cost && e[i].cost>=0 && e[i].cap>e[i].flow){
d[v]=d[u]+e[i].cost; pre[v]=i;
if(!vis[v]){
vis[v]=true; q.push(v);
}
}
}
}
return d[T]!=INF;
}
int mxf(){
int a,flow=0;
while(spfa()){
a=INF;
for(int pos=T,i=pre[T]; pos!=SS; pos=e[i].from,i=pre[pos]){
// D(pos); D(e[i].from); E;
a=min(a,e[i].cap-e[i].flow);
}
// D(a); E;
for(int pos=T,i=pre[T]; pos!=SS; pos=e[i].from,i=pre[pos]){
// D(pos); D(e[i].from); E;
e[i].flow+=a; e[i^1].flow-=a; totcost+=e[i].cost*a;
}
flow+=a;
// D(cost); D(flow); E;
}
}
inline bool inboard(int x,int y){ return x>=1 && x<=n && y>=1 && y<=n; }
void dfs(int x,int y,int op,int col,int f[105][105]){
if(!inboard(x,y) || g[x][y]!=0 || f[x][y]!=0) return;
f[x][y]=col; len[col]++;
if(op==0){ dfs(x,y-1,op,col,f); dfs(x,y+1,op,col,f); }
else{ dfs(x-1,y,op,col,f); dfs(x+1,y,op,col,f); }
}
void debug(){
cout<<"hang:\n";
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) cout<<hang[i][j]<<" ";
cout<<endl;
}
cout<<"lie\n";
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) cout<<lie[i][j]<<" ";
cout<<endl;
}
for(int i=1;i<=cnt;i++) D(len[i]); E;
}
void color(){
cnt=0;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
if(g[i][j]==0 && hang[i][j]==0) dfs(i,j,0,++cnt,hang);
}
pt=cnt;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
if(g[i][j]==0 && lie[i][j]==0) dfs(i,j,1,++cnt,lie);
}
// D(cnt); D(pt); E;
// debug();
}
void build(){
clear();
SS=cnt+1; S=SS+1; T=S+1;
// D(SS); D(S); D(T); E;
add2(SS,S,0,0);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(g[i][j]==0){
add2(hang[i][j],lie[i][j],1,0);
}
for(int i=1;i<=pt;i++){
for(int k=0;k<len[i];k++){
add2(S,i,1,k);
}
}
for(int i=pt+1;i<=cnt;i++){
for(int k=0;k<len[i];k++){
add2(i,T,1,k);
}
}
// for(int i=2;i<=ec;i++){ D(e[i].from); D(e[i].to); D(e[i].cap); E; }
}
int main(){
freopen("chess.in","r",stdin);
freopen("chess.out","w",stdout);
scanf("%d",&n); char str[105];
for(int i=1;i<=n;i++){
scanf("%s",str+1);
for(int j=1;j<=n;j++){
g[i][j]=str[j]=='.'?0:-1;
}
}
color(); build();
int q,k; scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%d",&que[i]); tp[i]=que[i];
}
sort(tp+1,tp+1+q); int tpcnt=unique(tp+1,tp+1+q)-tp-1;
// for(int i=1;i<=tpcnt;i++) cout<<tp[i]<<endl; E;
// D(tpcnt); E;
for(int i=1;i<=tpcnt;i++){
e[2].cap=tp[i];
// for(int j=2;j<=ec;j++) e[j].flow=0;
// totcost=0;
mxf(); ans[tp[i]]=totcost;
}
for(int i=1;i<=q;i++){
printf("%d\n",ans[que[i]]);
}
}
第二题:Tower
题目大意:
现在有一条[1,l]的数轴,要在上面造n座塔,每座塔的坐标要两两不同,且为整点。
塔有编号,且每座塔都有高度,对于编号为i的塔,其高度为i。
对于一座塔,需要满足它与前面以及后面的塔的距离大于等于自身高度(不存在则没有限制)。
问有多少建造方案。答案对m取模。塔不要求按编号为顺序建造。
数据规模:
n<=100,l<=109,m<=109
题解:
跟以前做过的一道苹果树的题目很像。
两座塔之间的距离为max(i,j),为了没有后效性,从小到大考虑。
这样两个塔之间如果又插入一个(更高)的塔,原来的空隙就会被破坏换成新塔的高度。
我们采用这样的思想:钦定两个塔之间是否还会被插入其它的更高的塔。只有确定两个塔之间不会加入其它的塔才将它们的距离计入。
设
f[i][j][k]
表示前i座塔,留有j个空隙,已经计入的长度是k,里面存的是方案数。
新加入一个塔,决策:
1. 放在两边,留一个空隙。
2. 放在两边,不留空隙。
3. 放在中间的一个空隙,留两个空隙。
4. 放在中间的一个空隙,留一个空隙。
5. 放在中间的一个空隙,不留空隙。
最后答案枚举k,这样还有
l−k
个空隙,把这些空隙插入进去,
隔板法,根据可以重复选择的组合数,
f[n][0][k]∗C(l−k+n,n)
Code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<ctime>
#define rep(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int N=105;
int dp[N][N][N*N];
int flag[N],prime[N],cnt;int c[N],tmp[N];
int sum[N];
int n,l,mod;
int ans;
void euler(){
int i,j;
rep(i,2,n){
if(!flag[i])prime[++cnt]=i;
for(j=1;j<=cnt&&i*prime[j]<=n;j++){
flag[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
}
int C(int k){
int ret=1;int i,j;int x;
memset(tmp,0,sizeof(tmp));
for(i=k;i>=k-n+1;i--){
x=i;
rep(j,1,cnt){
while(tmp[j]<c[j]&&x%prime[j]==0){
x/=prime[j];tmp[j]++;
}
}
ret=1ll*ret*x%mod;
}
return ret;
}
int main(){
freopen("tower.in","r",stdin);
freopen("tower.out","w",stdout);
int i,j,k;int x;
scanf("%d%d%d",&n,&l,&mod);l--;
euler();
rep(i,2,n){
x=i;
rep(j,1,cnt){
while(x%prime[j]==0){
c[j]++;x/=prime[j];
}
}
}
rep(i,1,n)sum[i]=sum[i-1]+2*i;
dp[1][0][0]=1;
rep(i,1,n-1){
rep(j,1,min(i-1,n-i)){
rep(k,0,sum[i]){
dp[i+1][j-1+0][k+(i+1)*(2-0)]=(dp[i+1][j-1+0][k+(i+1)*(2-0)]+1ll*j*dp[i][j][k])%mod;
dp[i+1][j-1+1][k+(i+1)*(2-1)]=(dp[i+1][j-1+1][k+(i+1)*(2-1)]+1ll*2*j*dp[i][j][k])%mod;
dp[i+1][j-1+2][k+(i+1)*(2-2)]=(dp[i+1][j-1+2][k+(i+1)*(2-2)]+1ll*j*dp[i][j][k])%mod;
}
}
rep(j,0,min(i-1,n-i)){
rep(k,0,sum[i]){
dp[i+1][j+0][k+(i+1)*(1-0)]=(dp[i+1][j+0][k+(i+1)*(1-0)]+1ll*2*dp[i][j][k])%mod;
dp[i+1][j+1][k+(i+1)*(1-1)]=(dp[i+1][j+1][k+(i+1)*(1-1)]+1ll*2*dp[i][j][k])%mod;
}
}
}
rep(k,0,sum[n]){
if(l<k) break;
if(dp[n][0][k]){
ans=(ans+1ll*dp[n][0][k]*C(l-k+n))%mod;
}
}
printf("%d",ans);
}