广州二中NOI2018模拟赛(三十七)
A.贪婪人

考虑一个DP
设dp[i][j][k]表示前i行j列中,a[i][j]=k时的方案数
转移也非常简单。如果某个格子左边的数是k,它本身是k+r,那么如果要从左边过来,它左下方那个格子就有r+1种取值,再下面那些格子可以任意取,因此有(s+1)^(H-i-1)种取值,于是dp[i][j][k]对dp[i][j+1][k+r]的贡献就是dp[i][j][k]*(r+1)*(s+1)^(H-i-1)
对下面那个格子的贡献也类似,具体看代码
时间复杂度O(HWS²),能拿70分
#include<bits/stdc++.h>
#define ha 10007
using namespace std;
const int N=110;
int gao[N][N][N];
int pw[N];
inline void add(int &x,const int &y){x=(x+y>=ha)?(x+y-ha):(x+y);}
int main()
{
int n,m,s;
scanf("%d%d%d",&m,&n,&s);
gao[1][1][0]=1;pw[0]=1;
for(int i=1;i<N;i++)
pw[i]=pw[i-1]*(s+1)%ha;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
for(int k=0;k<=s;k++)
for(int l=0;l+k<=s;l++)
{
if(j<n)
{
if(i<m) add(gao[i][j+1][k+l],1ll*gao[i][j][k]*(l+1)*pw[m-i-1]%ha);
else add(gao[i][j+1][k+l],gao[i][j][k]);
}
if(i<m)
{
if(j<n) add(gao[i+1][j][k+l],1ll*gao[i][j][k]*l*pw[n-j-1]%ha);
else add(gao[i+1][j][k+l],gao[i][j][k]);
}
}
printf("%d\n",gao[m][n][s]);
return 0;
}
发现在非边缘每次向下走,路径权值最少增加1,于是可以不用做那么多的DP,只要做到第S+1行就够了
然后DP的时候用前缀和优化一下,就可以达到O(WS²)的复杂度了!
最后统计答案还要再跑一个O(HS)的DP,这个很简单,看代码就好了
#include<bits/stdc++.h>
#define ha 10007
using namespace std;
int gao[110][2510][110];
int pw[2510],f[2510][110];
int s1[5+2510],s2[5+2510];
inline void add(int &x,const int &y){x=(x+y>=ha)?(x+y-ha):(x+y);}
inline int Add(const int &x,const int &y){return (x+y>=ha)?(x+y-ha):(x+y);}
inline int dev(const int &x,const int &y){return (x-y<0)?(x-y+ha):(x-y);}
int main()
{
int m,n,s;
scanf("%d%d%d",&m,&n,&s);
gao[1][1][0]=pw[0]=1;
for(int i=1;i<=2500;i++)
pw[i]=pw[i-1]*(s+1)%ha;
for(int i=1;i<=m&&i<=s+1;i++)
for(int j=1;j<=n;j++)
{
s1[5+0]=s2[5+0]=gao[i][j][s];
for(int k=1;k<=s;k++)
s1[5+k]=(s1[5+k-1]+gao[i][j][s-k]*(k+1))%ha,
s2[5+k]=Add(s2[5+k-1],gao[i][j][s-k]);
for(int k=0;k<=s;k++)
if(i<m) add(gao[i][j+1][k],((s1[5+s]-s1[5+s-k-1]-(s2[5+s]-s2[5+s-k-1])*(s-k))%ha+ha)*pw[m-i-1]%ha);
else if(j<n) add(gao[i][j+1][k],dev(s2[5+s],s2[5+s-k-1]));
s1[5+0]=0;
for(int k=1;k<=s;k++)
s1[5+k]=(s1[5+k-1]+gao[i][j][s-k]*k)%ha;
if(i==m) continue;
for(int k=0;k<=s;k++)
{
if(j<n) add(gao[i+1][j][k],((s1[5+s]-s1[5+s-k-1]-(s2[5+s]-s2[5+s-k-1])*(s-k))%ha+ha)*pw[n-j-1]%ha);
else add(gao[i+1][j][k],dev(s2[5+s],s2[5+s-k-1]));
}
}
if(m<=s+1){printf("%d\n",gao[m][n][s]);return 0;}
for(int i=s+1;i<m;i++)
{
for(int k=0;k<=s;k++)
s2[5+k]=Add(s2[5+k-1],(i==s+1)?gao[i][n][s-k]:f[i][s-k]);
for(int k=0;k<=s;k++)
add(f[i+1][k],dev(s2[5+s],s2[5+s-k-1]));
}
printf("%d\n",f[m][s]);
return 0;
}
B.社会人

场上脑残,没想到70分做法,写了个爆搜,本以为70分的数据会有梯度,没想到直接爆0
贴一下爆搜程序,对拍用
#include<bits/stdc++.h>
using namespace std;
struct Edge{int u,v,next;bool tag;} e[60];
int h[30],tot,n,m,T;
double k;
bool vis[30];
double ans=0;
inline int get(int i,int j){return (i-1)*n+j;}
void AddEdge(int u,int v)
{
e[++tot].u=u;
e[tot].v=v;
e[tot].next=h[u];
e[tot].tag=0;
h[u]=tot;
}
void add_edge(int u,int v)
{
AddEdge(u,v);
AddEdge(v,u);
}
bool gao()
{
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(get(1,1));
while(!q.empty())
{
int u=q.front();vis[u]=1;q.pop();
for(int tmp=h[u];tmp;tmp=e[tmp].next)
if(e[tmp].tag&&(!vis[e[tmp].v]))
q.push(e[tmp].v);
}
return vis[get(m,n)];
}
void gao(int now,double p)
{
if(now>tot)
{
if(gao()) ans+=p;
return;
}
gao(now+2,p*k);
e[now].tag=e[now+1].tag=1;
gao(now+2,p*(1-k));
e[now].tag=e[now+1].tag=0;
}
int main()
{
for(scanf("%d",&T);T;T--)
{
scanf("%d%d%lf",&n,&m,&k);
ans=0;tot=0;
memset(h,0,sizeof(h));
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
if(j<n) add_edge(get(i,j),get(i,j+1));
if(i<m) add_edge(get(i,j),get(i+1,j));
}
gao(1,1);
printf("%.7lf\n",ans);
}
return 0;
}
试想,假如我们知道了在一个m*n的网格图中,连接k条边,使得(1,1)与(m,n)联通的方案数(这个方案数记作gao[m][n][k]),我们就可以搞出答案
设tot为m*n网格图的总边数(即tot=n*(m-1)+m*(n-1)),那么连接某k条边,其它的边不连的概率就是(1-p)^k*p^(tot-k)
于是连接k条边时,对答案的贡献就是gao[m][n][k]*(1-p)^k*p^(tot-k)
那么只要得到了这个gao数组,我们就可以在O(tot)的时间内求出答案
gao数组可以打表弄出来。打表程序跑了30s……不过这不是问题,反正是本地的时间
打表程序源码:
#include<bits/stdc++.h>
using namespace std;
struct Edge{int u,v,next;bool tag;} e[60];
int h[30],tot,n,m,T,fuck;
double k;
bool vis[30];
int ans;
inline int get(int i,int j){return (i-1)*n+j;}
void AddEdge(int u,int v)
{
e[++tot].u=u;
e[tot].v=v;
e[tot].next=h[u];
e[tot].tag=0;
h[u]=tot;
}
void add_edge(int u,int v)
{
AddEdge(u,v);
AddEdge(v,u);
}
bool gao()
{
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(get(1,1));
while(!q.empty())
{
int u=q.front();vis[u]=1;q.pop();
for(int tmp=h[u];tmp;tmp=e[tmp].next)
if(e[tmp].tag&&(!vis[e[tmp].v]))
q.push(e[tmp].v);
}
return vis[get(m,n)];
}
void gao(int now,int g)
{
if(g>=fuck)
{
if(gao()) ans++;
return;
}
if(now>tot) return;
gao(now+2,g);
e[now].tag=e[now+1].tag=1;
gao(now+2,g+1);
e[now].tag=e[now+1].tag=0;
}
int main()
{
freopen("gao.out","w",stdout);
for(m=1;m<=4;m++)
for(n=1;n<=4;n++)
{
tot=0;
memset(h,0,sizeof(h));
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
if(j<n) add_edge(get(i,j),get(i,j+1));
if(i<m) add_edge(get(i,j),get(i+1,j));
}
for(fuck=0;fuck<=tot/2;fuck++)
{
ans=0;
gao(1,0);
printf("gao[%d][%d][%d]=%d;\n",m,n,fuck,ans);
}
//printf("%.7lf\n",ans);
}
return 0;
}
70分代码:
#include<bits/stdc++.h>
using namespace std;
double gao[7][7][30];
double p1[30],p2[30];
void gaogao()
{
gao[1][1][0]=1;
gao[1][2][0]=0;
gao[1][2][1]=1;
gao[1][3][0]=0;
gao[1][3][1]=0;
gao[1][3][2]=1;
gao[1][4][0]=0;
gao[1][4][1]=0;
gao[1][4][2]=0;
gao[1][4][3]=1;
gao[2][1][0]=0;
gao[2][1][1]=1;
gao[2][2][0]=0;
gao[2][2][1]=0;
gao[2][2][2]=2;
gao[2][2][3]=4;
gao[2][2][4]=1;
gao[2][3][0]=0;
gao[2][3][1]=0;
gao[2][3][2]=0;
gao[2][3][3]=3;
gao[2][3][4]=12;
gao[2][3][5]=17;
gao[2][3][6]=7;
gao[2][3][7]=1;
gao[2][4][0]=0;
gao[2][4][1]=0;
gao[2][4][2]=0;
gao[2][4][3]=0;
gao[2][4][4]=4;
gao[2][4][5]=24;
gao[2][4][6]=61;
gao[2][4][7]=76;
gao[2][4][8]=40;
gao[2][4][9]=10;
gao[2][4][10]=1;
gao[3][1][0]=0;
gao[3][1][1]=0;
gao[3][1][2]=1;
gao[3][2][0]=0;
gao[3][2][1]=0;
gao[3][2][2]=0;
gao[3][2][3]=3;
gao[3][2][4]=12;
gao[3][2][5]=17;
gao[3][2][6]=7;
gao[3][2][7]=1;
gao[3][3][0]=0;
gao[3][3][1]=0;
gao[3][3][2]=0;
gao[3][3][3]=0;
gao[3][3][4]=6;
gao[3][3][5]=48;
gao[3][3][6]=166;
gao[3][3][7]=312;
gao[3][3][8]=334;
gao[3][3][9]=192;
gao[3][3][10]=64;
gao[3][3][11]=12;
gao[3][3][12]=1;
gao[3][4][0]=0;
gao[3][4][1]=0;
gao[3][4][2]=0;
gao[3][4][3]=0;
gao[3][4][4]=0;
gao[3][4][5]=10;
gao[3][4][6]=120;
gao[3][4][7]=661;
gao[3][4][8]=2179;
gao[3][4][9]=4718;
gao[3][4][10]=6929;
gao[3][4][11]=6897;
gao[3][4][12]=4568;
gao[3][4][13]=2065;
gao[3][4][14]=643;
gao[3][4][15]=134;
gao[3][4][16]=17;
gao[3][4][17]=1;
gao[4][1][0]=0;
gao[4][1][1]=0;
gao[4][1][2]=0;
gao[4][1][3]=1;
gao[4][2][0]=0;
gao[4][2][1]=0;
gao[4][2][2]=0;
gao[4][2][3]=0;
gao[4][2][4]=4;
gao[4][2][5]=24;
gao[4][2][6]=61;
gao[4][2][7]=76;
gao[4][2][8]=40;
gao[4][2][9]=10;
gao[4][2][10]=1;
gao[4][3][0]=0;
gao[4][3][1]=0;
gao[4][3][2]=0;
gao[4][3][3]=0;
gao[4][3][4]=0;
gao[4][3][5]=10;
gao[4][3][6]=120;
gao[4][3][7]=661;
gao[4][3][8]=2179;
gao[4][3][9]=4718;
gao[4][3][10]=6929;
gao[4][3][11]=6897;
gao[4][3][12]=4568;
gao[4][3][13]=2065;
gao[4][3][14]=643;
gao[4][3][15]=134;
gao[4][3][16]=17;
gao[4][3][17]=1;
gao[4][4][0]=0;
gao[4][4][1]=0;
gao[4][4][2]=0;
gao[4][4][3]=0;
gao[4][4][4]=0;
gao[4][4][5]=0;
gao[4][4][6]=20;
gao[4][4][7]=360;
gao[4][4][8]=3066;
gao[4][4][9]=16332;
gao[4][4][10]=60670;
gao[4][4][11]=165956;
gao[4][4][12]=344033;
gao[4][4][13]=547944;
gao[4][4][14]=672928;
gao[4][4][15]=635632;
gao[4][4][16]=460680;
gao[4][4][17]=258144;
gao[4][4][18]=112742;
gao[4][4][19]=38392;
gao[4][4][20]=10071;
gao[4][4][21]=1976;
gao[4][4][22]=274;
gao[4][4][23]=24;
gao[4][4][24]=1;
}
int main()
{
gaogao();
int T,m,n;
double p;
for(scanf("%d",&T);T;T--)
{
scanf("%d%d%lf",&m,&n,&p);
if(n==1&&m==1){puts("1");continue;}
int tot=n*(m-1)+m*(n-1);
p1[0]=p2[0]=1;
for(int i=1;i<=tot;i++)
p1[i]=p1[i-1]*(1-p),p2[i]=p2[i-1]*p;
double ans=0;
for(int i=1;i<=tot;i++)
ans+=gao[m][n][i]*p1[i]*p2[tot-i];
printf("%.7lf\n",ans);
}
return 0;
}
你问我100分做法?那你自己去学插头DP吧,反正我不会
C.周树人

显然是一道乱搞题
如何用36行代码得到39分?
太简单了!只要默认这棵树是一条链,然后枚举每一个点,贪心地选取与自己距离近的点为儿子就好了!当然需要并查集来维护一下,以确保不会选择已经在树上的点
#include<bits/stdc++.h>
using namespace std;
const int N=410;
struct Point{int x,y;} p[N];
int fa[N],son[N];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int dist(Point a,Point b){return (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);}
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++)
{
int mx=0x7fffffff;
for(int j=1;j<=n;j++)
{
if(i==j) continue;
int f1=find(i),f2=find(j);
if(f1==f2) continue;
int dis=dist(p[i],p[j]);
if(dis<mx) son[i]=j,mx=dis;
}
if(son[i]) fa[find(son[i])]=fa[find(i)];
}
for(int i=1;i<=n;i++)
if(son[i]) printf("%d %d\n",i,son[i]);
}
return 0;
}
如何用47行代码得到65分?
太简单了!只要先把所有点两两之间连好边,边权设为距离,然后跑一遍最小生成树就可以了!
卧槽这么随便就拿了65分???为什么我场上没想到啊呜……
#include<bits/stdc++.h>
using namespace std;
struct Edge{int u,v,c;} e[160010];
struct Point{int x,y;} p[410];
int h[410],sum,fa[410];
bool operator < (const Edge &a,const Edge &b){return a.c<b.c;}
int dist(const Point &a,const Point &b){return (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void gao(int n)
{
sort(e+1,e+1+sum);
int cnt=1;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=sum;i++)
{
int u=e[i].u,v=e[i].v;
int fu=find(u),fv=find(v);
if(fu==fv) continue;
fa[fv]=fu;cnt++;
printf("%d %d\n",u,v);
if(cnt==n) break;
}
}
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
memset(h,0,sizeof(h));sum=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j)
{
e[++sum].u=i;
e[sum].v=j;
e[sum].c=dist(p[i],p[j]);
}
gao(n);
}
return 0;
}
什么?你问我怎么拿100分?醒醒吧,zjt都还没搞出100分做法!