西安铁一中2018暑期集训第二期模拟赛DAY1题解
T1:聚会
- 题目大意:有n个人和m对关系,现在要选s个人使得这s个人都互相认识,求方案数。
n≤100,m≤1000,s≤10
,且保证每个人认识的不超过
20
人。
- 题解:考虑到每个人最多认识
20
人,而
C209=167960
,并不是一个太大的数字,因此我们可以考虑暴力选人然后check一下,看上去复杂度好像刚好爆掉了,但如果我们选人时按照编号从小到大选,既不会造成答案重复,也能过减小很大一部分复杂度,因此建边时可以只建由小指向大的边。
- 代码里因为并没有超时,所以还是存了无向边。
#include<bits/stdc++.h>
#define maxn 100005
#define inf (1<<30)
#define mod 1000000007
using namespace std;
typedef long long LL;
int read()
{
char c=getchar();int f=1,sum=0;
while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
int n,m,s;
int a[105][105];
int deg[105],sta[105],vis[105],ans;
int head[105],to[2005],nex[2005],cnt;
void add(int u,int v)
{
to[++cnt]=v;nex[cnt]=head[u];head[u]=cnt;
}
bool check()
{
for(int i=1;i<s;i++)
for(int j=i+1;j<s;j++)
if(!a[sta[i]][sta[j]]) return false;
return true;
}
void dfs(int x,int num,int l)
{
if(num==s)
{
if(check())
ans++;
return;
}
for(int i=head[x];i;i=nex[i])
{
if(deg[to[i]]<s-1) continue;
if(to[i]<l) continue;
if(vis[to[i]]) continue;
sta[num]=to[i];
vis[to[i]]=1;
dfs(x,num+1,to[i]);
sta[num]=0;
vis[to[i]]=0;
}
return;
}
int main()
{
freopen("counting.in","r",stdin);
freopen("counting.out","w",stdout);
n=read();m=read();s=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
a[u][v]=a[v][u]=1;
deg[u]++;deg[v]++;
add(u,v);add(v,u);
}
for(int i=1;i<=n-s+1;i++)
dfs(i,1,i);
printf("%d\n",ans);
return 0;
}
T2:扔骰子
- 本场最难题
- 题目大意:
n
个人
(n≤10)
扔骰子,每个人有一个猜测序列(猜测序列长度
l≤10
),最先扔出猜测序列的人获胜,求每个人获胜概率。
- 题解:AC自动机+高斯消元。
- 对于所有序列先建立AC自动机然后进行高斯消元解出答案。
- 如果一个节点
i
不是结尾的节点,那么对于
i
能转移到的每个节点
j
都有
16
的概率转移到,因此对于方程
A[i][j]
+=
16
,然后自己应该在方程的等号右边,直接令
A[i][i]
+=
−1
。
- 根节点特殊需要特殊处理,因为一开始就以1的概率转移到根节点,所以
A[0]=−1
。
- 放上一张图解:
- 然后高斯消元即可。
#include<bits/stdc++.h>
#define maxn 100005
#define inf (1<<30)
#define eps 1e-6
using namespace std;
typedef long long LL;
int read()
{
char c=getchar();int f=1,sum=0;
while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
int T;
int n,l;
int siz;
int son[505][7],fail[5005],vis[5005],q[5005];
int ed[15],s[15];
double ans[201][201];
void insert(int x)
{
int now=0;
for(int i=1;i<=l;i++)
{
int t=s[i];
if(son[now][t]) now=son[now][t];
else now=son[now][t]=++siz;
}
vis[now]++;
ed[x]=now;
}
void build()
{
queue<int> q;
q.push(0);
while(!q.empty())
{
int now=q.front();q.pop();
for(int i=1;i<=6;i++)
{
int nex=son[now][i];
if(!nex)
son[now][i]=son[fail[now]][i];
else
{
if(now)
fail[nex]=son[fail[now]][i];
q.push(nex);
}
}
}
}
void solve()
{
for(int i=0;i<=siz;i++)
{
if(vis[i]) continue;
for(int j=1;j<=6;j++)
ans[son[i][j]][i]+=1.0/6.0;
}
for(int i=0;i<=siz;i++)
ans[i][i]+=-1.0;
ans[0][siz+1]=-1.0;
}
void gauss(int x)
{
int now=0,to;
double t;
for(int i=0;i<=x;i++)
{
for(to=now;to<=x;to++)
if(fabs(ans[to][i])>eps)
break;
if(to>x) continue;
if(to!=now)
{
for(int j=0;j<=x+1;j++)
swap(ans[to][j],ans[now][j]);
}
t=ans[now][i];
for(int j=0;j<=x+1;j++)
ans[now][j]/=t;
for(int j=0;j<=x;j++)
if(j!=now)
{
t=ans[j][i];
for(int k=0;k<=x+1;k++)
ans[j][k]-=t*ans[now][k];
}
now++;
}
}
void init()
{
siz=0;
memset(son,0,sizeof(son));
memset(ans,0.0,sizeof(ans));
memset(vis,0,sizeof(vis));
memset(fail,0,sizeof(fail));
}
int main()
{
T=read();
while(T--)
{
init();
n=read();l=read();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=l;j++) s[j]=read();
insert(i);
}
build();
solve();
gauss(siz);
double sum=0.0;
for(int i=1;i<=n;i++) sum+=ans[ed[i]][siz+1];
for(int i=1;i<n;i++) printf("%.6lf ",ans[ed[i]][siz+1]/sum);
printf("%.6lf\n",ans[ed[n]][siz+1]/sum);
}
return 0;
}
T3:开灯
- 题意:在一条数轴上有
n
个灯,第
i
盏灯在
d[i]
处,按下开关
t[i]
秒后熄灭,求一个合法序列使得能够按亮所有的灯,在两盏灯间的移动时间是两个灯的坐标之差。
- 题解:区间dp。用
dp[l][r][0/1]
表示按亮
[l,r]
之间的灯,并且停留在左端点还是右端点的最短时间。
- 转移方程为:
-
dp[l][r][0]=min(dp[l+1][r][0]+d[l+1]−d[l],dp[l+1][r][1]+d[r]−d[l])
-
dp[l][r][1]=min(dp[l][r−1][0]+d[r]−d[l],dp[l][r−1][1]+d[r]−d[r−1])
- 记录路径时只要记录当前这个状态是往左还是往右走即可。
#include<bits/stdc++.h>
#define maxn 100005
#define inf (1<<30)
#define mod 1000000007
using namespace std;
typedef long long LL;
int read()
{
char c=getchar();int f=1,sum=0;
while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
int n;
int t[205],d[205];
int dp[205][205][2],pre[205][205][2];
int main()
{
while(n=read())
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++) t[i]=read();
for(int i=1;i<=n;i++) d[i]=read();
for(int l=2;l<=n;l++)
{
for(int i=1;i<=n-l+1;i++)
{
int j=i+l-1;
int li=dp[i+1][j][0]+d[i+1]-d[i];
int ri=dp[i+1][j][1]+d[j]-d[i];
if(li<=ri) dp[i][j][0]=li,pre[i][j][0]=0;
else dp[i][j][0]=ri,pre[i][j][0]=1;
if(t[i]<=dp[i][j][0])
dp[i][j][0]=inf;
li=dp[i][j-1][0]+d[j]-d[i];
ri=dp[i][j-1][1]+d[j]-d[j-1];
if(li<=ri) dp[i][j][1]=li,pre[i][j][1]=0;
else dp[i][j][1]=ri,pre[i][j][1]=1;
if(t[j]<=dp[i][j][1])
dp[i][j][1]=inf;
}
}
int l,r;
int dir;
if(dp[1][n][0]<inf)
{
dir=pre[1][n][0];
printf("1");
l=2,r=n;
}
else if(dp[1][n][1]<inf)
{
dir=pre[1][n][1];
printf("%d",n);
l=1,r=n-1;
}
else
{
puts("Mission Impossible");
continue;
}
while(l<=r)
{
if(dir==0)
{
dir=pre[l][r][0];
printf(" %d",l++);
}
else
{
dir=pre[l][r][1];
printf(" %d",r--);
}
}
printf("\n");
}
return 0;
}