swust 2014届选拔赛 题解

时间:2022-12-20 23:35:52

   题目传送门:http://www.oj.swust.edu.cn/contest/show/1160

A: 快乐的Fm

这是一道dp。我们假设这n个人按照水平排成一排,   可以很容易想到用dp[i][j]表示: 排到第第i个位置的人面向左边的是第j个部位的总方案数。可以有转移

  dp[i][j]+=dp[i-1][k] ( 只要k和j 不发生冲突就可以转移 )。总复杂度(O(16n))

AC 代码。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 100010;
const int mod = 522462628;
int dir[mmax];
int dp[mmax][5];
int main()
{
int n;
#ifdef debug
freopen("in.txt","r",stdin);
#endif
while(cin>>n)
{
for(int i=1;i<=n;i++)
scanf("%d",&dir[i]);
memset(dp,0,sizeof dp);
dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=1;
for(int i=2;i<=n;i++)
{
for(int j=1;j<=4;j++)
{
for(int k=1;k<=4;k++)
{
if(k==2 && abs(j-dir[i-1])==2 )
continue;
if(j==4 && k==dir[i])
continue;
dp[i][j]+=dp[i-1][k];
dp[i][j]%=mod;
}
}
}
int ans=0;
for(int i=1;i<=4;i++)
{
ans+=dp[n][i];
ans%=mod;
}
cout<<ans<<endl;
}
return 0;
}

B: 奔跑的Fm

按照题意实际上只要是的所有的点都能联通就可以了。因为走过的路程不需要在走了,所以实际上就是求一个最小生成树。只是这里的边的花费是2维的,那么我们可以自定义边权按照 len最小,其次按照cost最小排序。由于点只有1000个  ,所以用prim和kruskal算法都能通过。复杂度分别为O(n^2)和O(mlgm);
kruskal代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#include <map>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 1010;
struct edge
{
int st,en;
int len,cost;
bool operator < (const edge &a) const
{
if(len==a.len)
return cost<a.cost;
return len<a.len;
}
}E[10010];
map<string,int>q;
bool ok(int x)
{
while(x)
{
if(x%10 == 9)
return 0;
x/=10;
}
return 1;
}
int fa[mmax];
int find(int x)
{
if(x==fa[x])
return x;
return fa[x]=find(fa[x]);
}
int main()
{
#ifdef debug
freopen("in.txt","r",stdin);
#endif
int n,m,len,cost,cnt,num;
string u,v;
while(cin>>n>>m)
{
q.clear();
cnt=num=0;
for(int i=0;i<m;i++)
{
cin>>u>>v>>len>>cost;
if(!q.count(u))
q[u]=++cnt;
if(!q.count(v))
q[v]=++cnt;
if(ok(len) && ok(cost))
{
E[num].st=q[u];
E[num].en=q[v];
E[num].len=len;
E[num].cost=cost;
num++;
}
}
for(int i=1;i<=n;i++)
fa[i]=i;
sort(E,E+num);
len=0,cost=0;
for(int i=0;i<num;i++)
{
int u=find(E[i].st);
int v=find(E[i].en);
if(u!=v)
{
fa[u]=v;
len+=E[i].len;
cost+=E[i].cost;
}
}
cnt = 0;
for(int i=1;i<=n;i++)
{
if(find(i)==i)
cnt++;
}
if(cnt>=2)
puts("Don't touch me!");
else
cout<<"totlen: "<<len<<" mincost: "<<cost<<endl;
}
return 0;
}

C: 幸运的Fm


关于区间查询有的计数问题,只有查询没有更新的这类问题我也讲过了,首先我们预处理出每个位置的f(a[i])的值,然后,然后用sum[i]记录到第i个位置的前缀和,那么要查询区间[l,r]上的答案是, ans=sum[r]-sum[l-1] (根本用不到线段树,线段树是用来处理有更新的)。现在的关键问题在于怎么快速求出每个a[i]的f值。有一种的n√a(max)的做法,就是对于每个a[i]暴力质因子分解。    还有一种很高效的方法。  如果你发现 f[x]=f[x/d]+1(d是x的一个质因子),那么如果对于每一个数我们找出了他的一个质因子,我们就可 O(n)递推出所有的1-1000000的f值。  对于求出每个数的一个质因子,我们可以按照素数筛选那样,只是标记哪里多一个记录质因子。  复杂是nlga(max)。  这题本来一开始数据是卡了√n的做法的。后来在我的强烈要求下时间限制标称2s。放过了这种做法。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#include<queue>
#define debug
using namespace std;
const int mmax = 1000010;
int num[mmax],c[mmax];
int sum[50010];
bool isprime[mmax];
void pre()//nlgn的预处理方法
{
memset(isprime,1,sizeof isprime);
num[1]=0;
isprime[1]=0;
for(int i=2;i<mmax;i++)
{
if(isprime[i])
{
for(int j=2*i;j<mmax;j+=i)
{
isprime[j]=0;
c[j]=i;
}
}
}
for(int i=2;i<mmax;i++)
{
if(isprime[i])
num[i]=1;
else
num[i]=num[i/c[i]]+1;
}
}
int ff(int x)//暴力质因子分解
{
int cnt;
for(int i=2;i*i<=x;i++)
{
while(x%i==0)
{
cnt++;
x/=i;
}
}
if(x>1)
cnt++;
return cnt;
}
int main()
{
int n,q;
#ifdef debug
freopen("in.txt","r",stdin);
#endif
pre();
while(cin>>n>>q&& n+q)
{
sum[0]=0;
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
sum[i]=sum[i-1]+num[x];// +ff(x) 为暴力
}
while(q--)
{
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",sum[r]-sum[l-1]);
}
}
return 0;
}

D: 苦恼的Fm

这题最后实际上就是2个人比赛了。对于n-1个人  只需要最大的m张牌就够了,其他的牌都没有用的。  有2种做法,第一种是贪心。对于fm出的最大的牌,对方如果能赢,就用最大的牌去赢了好了,就是对于fm的牌,我么重大到小如果能赢就赢,这么保证最优。   另外一种做法。对于fm的每一张牌i,对于对面的牌j如果能赢i(j>i)就建立边i->j。那么你会发现最后是一个2分图,如果要fm赢得最少,就要选出尽量多的边,没选一条边代表Fm输了,由于每张牌只能选一次,那么实际上就是在2分图上面找到最大匹配。当然贪心的做法复杂度最好 O(nlgn) 贪心做法:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define debug
bool flag[10005];
int s[10005];
int main()
{
#ifdef debug
freopen("in.txt","r",stdin);
#endif
int n,m;
while(~scanf("%d%d",&n,&m))
{
memset(flag,false,sizeof(flag));
for(int i=0;i<m;i++)
{
scanf("%d",&s[i]);
flag[s[i]]=true;
}
sort(s,s+m);
int ans=0;
for(int i=0;i<m;i++)
{
bool k=false;
for(int j=s[i]+1;j<=n*m;j++)
{
if(!flag[j])
{
k=true;
flag[j]=true;
break;
}
}
if(!k)
ans++;
}
printf("%d\n",ans);
}
return 0;
}

匹配做法:
/*
* Author: islands
* Created Time: 2015/8/14 14:38:28
* File Name: stand.cpp
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#include <map>
#define debug
using namespace std;
typedef long long LL;
const int inf = 0x3fffffff;
const int mmax = 110;
bool G[mmax][mmax];
int card1[mmax];
int card2[mmax];
map<int,int>q;
int link[mmax];
bool vis[mmax];
bool match(int x,int n)
{
for(int i=0;i<n;i++)
{
if( G[x][i] && !vis[i])
{
vis[i]=1;
if(link[i]==-1 || match(link[i],n))
{
link[i]=x;
return 1;
}
}
}
return 0;
}
int hungury(int n)
{
int cnt=0;
memset(link,-1,sizeof link);
for(int i=0;i<n;i++)
{
memset(vis,0,sizeof vis);
if(match(i,n))
cnt++;
}
return cnt;
}
int main()
{
#ifdef debug
freopen("in.txt","r",stdin);
#endif
int n,m;
while(cin>>n>>m)
{
memset(G,0,sizeof G);
q.clear();
for(int i=0;i<m;i++)
{
scanf("%d",&card1[i]);
q[card1[i]]=1;
}
int cnt=0;
for(int i=n*m;i>=1;i--)
{
if(!q.count(i) && cnt<m)
card2[cnt++]=i;
}
for(int i=0;i<m;i++)
{
for(int j=0;j<m;j++)
{
if(card1[i]<card2[j])
G[i][j]=1;
}
}
printf("%d\n",m-hungury(m));

}
return 0;
}

E: Fm的旅行

最后题目就是求生成树的个数,由于边权都是1,就不存在最小生成树了(生成树都是最小了)。 我们考虑每一个边上的正m边形。 如果是生成树,那么不能形成环,首先假设每个m边形都是自联通(不通过其他的m边形),那么显然会形成一个环,每个m边形都可以通过其他m边形联通,又自联通,所以就形成环。那么我们需要有一个m变形不自联通,这样就不会形成 环,而且保证所有点都联通(自己YY一下)。  这个m边形有n个, 选出一个不自连通, 然后其他的都要自联通,每个m自联通只需要在删除一条边,所以有m种方法, 而对于不自联通的一个m变形,首先n变形上的边是不能选的,还要删除一条边,那么就有m-1种方法。 总的方案数 为  ans = n*(m-1)*m^(n-1)。
VIEW CODE
/*
* Author: islands
* Created Time: 2015/8/14 14:38:28
* File Name: stand.cpp
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#include <map>
#define debug
using namespace std;
typedef long long LL;
const int inf = 0x3fffffff;
const int mmax = 110;
const int mod = 10007;
int Pow_mod(int x,int y)
{
int res=1;
for(;y;y/=2)
{
if(y&1)
res=res*x%mod;
x=x*x%mod;
}
return res;
}
int main()
{
#ifdef debug
freopen("in.txt","r",stdin);
#endif
int n,m;
while(cin>>n>>m)
{
int ans=Pow_mod(m,n-1);
ans*=n;
ans%=mod;
ans*=(m-1);
ans%=mod;
cout<<ans<<endl;
}
return 0;
}

F: 只要出题人开心就好

纯粹的数学变换。

方法一


把(Xi+Yj)*(i+j) 拆开  有  Xi*(i+j) +Yj*(i+j)
分别考虑  Xi,Yj被乘了多少。 对于一个Xi  会枚举 j =1 -> m 那么 
就有 Xi*(i+1+i+2+i+3+i+4...+i+m  )  化简有 Xi*(m*i  +   (1+m)*m/2  )
同理 Yj*(j+1+j+2+j+3+...+j+n)  化简有Yj(n*j+(1+n)*n/2)
那么 最后 有
LL ans=0;
for(int i=1;i<=n;i++)
ans+=Xi*(m*i+(1+m)*m/2);
for(int i=1;i<=m;i++)
ans+=Yi*(n*i+(1+n)*n/2);
注意会超出int 范围
本题很好想,考验代码能力,当然想清楚了就写得很快。


方法二 
由于 1<=Xi,Yj<=1000
而 N 是 10^5,必然Xi有很多重复,很多都是重复计算
用一个数组c1[i] ,表示 Xi中每个数i出现的次数
用一个数组c2[i] ,表示 Yi中每个数i出现的次数
用一个数组d1[i] ,表示 Xi中每个数i乘的系数
用一个数组d2[i] ,表示 Yi中每个数i乘的系数
那么有
for(int x=1;x<=1000;x++)
for(int y=1;y<=1000;y++)
{
if(c1[x]>0 &&c2[y]>0)
ans+=(x+y)*(c1[x]*d2[y]+c2[y]*d1[x]);
}


其中 当输入一个Xi   c1[Xi]++,d1[Xi]+=i;

VIEW CODE  
/*
* Author: islands
* Created Time: 2015/8/14 14:38:28
* File Name: stand.cpp
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
typedef long long LL;
const int inf = 0x3fffffff;
const int mmax = 100010;
LL sum[31][2];
int Size[31];
int main()
{
#ifdef debug
freopen("in.txt","r",stdin);
#endif
int k,x;
while(cin>>k)
{
for(int i=1;i<=k;i++)
{
scanf("%d",&Size[i]);
sum[i][0]=sum[i][1]=0;
for(int j=1;j<=Size[i];j++)
{
scanf("%d",&x);
sum[i][0]+=x;
sum[i][1]+=x*j;
}
}
printf("0\n");
for(int i=2;i<=k;i++)
{
LL ans=1LL*Size[i-1]*sum[i][1]+1LL*Size[i]*sum[i-1][1];
ans+=1LL*(Size[i]+1)*Size[i]/2*sum[i-1][0];
ans+=1LL*(Size[i-1]+1)*Size[i-1]/2*sum[i][0];
printf("%lld\n",ans);
}
}
return 0;
}

G: islands的PhotoGraph

纯模拟,都告诉了对应坐标变换关系了,只要按照题意模写就好了。。。。。不需要任何思维难度 VIEW CODE
/*
* Author: islands
* Created Time: 2015/8/13 21:49:13
* File Name: stand.cpp
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 300;
int Photo[mmax][mmax];
int main()
{
#ifdef debug
freopen("in.txt","r",stdin);
#endif
int n,m;
while(~scanf("%d %d",&n,&m))
{
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
scanf("%d",&Photo[i][j]);
int q;
cin>>q;
while(q--)
{
int d,k;
cin>>d>>k;
if(d==0)
{
for(int i=0;i<(n/k);i++)
{
for(int j=0;j<(m/k);j++)
{
printf("%d%c",Photo[i*k][j*k],(j==(m/k)-1)?'\n':' ');
}
}
}
else
{
for(int i=0;i<n*k;i++)
{
for(int j=0;j<m*k;j++)
{
printf("%d%c",Photo[i/k][j/k],(j==m*k-1)?'\n':' ');
}
}
}
}
}
return 0;
}

H: Crysis 3

求区间内最大值的位置,取最靠左的那个的位置。单点修改。

思路:用线段树维护区间内的最大值和最大值最靠左的位置,记录2个值,一个记录最大值,另一个几率对应位置。实际上只用开一个值,只用记录位子即可,因为位置包含着最大值。

/*
* Author: islands
* Created Time: 2015/8/14 14:38:28
* File Name: stand.cpp
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 100010;
int A[mmax];
struct node
{
int l,r;
int pos,max_val;
int mid()
{
return (l+r)>>1;
}
}T[4*mmax];
void push_up(node &fa,node &ls,node& rs)
{
fa.l=ls.l,fa.r=rs.r;
fa.max_val=max(ls.max_val,rs.max_val);
if(fa.max_val==ls.max_val)
{
fa.pos=ls.pos;
return ;
}
fa.pos=rs.pos;

}
void build(int id,int l,int r)
{
T[id].l=l,T[id].r=r;
if(l==r)
{
T[id].pos=l;
T[id].max_val=A[l];
return ;
}
int mid=T[id].mid();
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
push_up(T[id],T[id<<1],T[id<<1|1]);
}
void update(int id,int pos,int val)
{
if(T[id].l==T[id].r)
{
T[id].max_val=val;
return ;
}
int mid=T[id].mid();
if(mid>=pos)
update(id<<1,pos,val);
else
update(id<<1|1,pos,val);
push_up(T[id],T[id<<1],T[id<<1|1]);
}
node query(int id,int l,int r)
{
if(l<= T[id].l && T[id].r <=r)
return T[id];
int mid=T[id].mid();
node tmp[3];
int t=0;
if(mid>=l)
tmp[1]=query(id<<1,l,r),t++;
if(mid<r)
tmp[2]=query(id<<1|1,l,r),t+=2;
if(t<3)
return tmp[t];
push_up(tmp[0],tmp[1],tmp[2]);
return tmp[0];
}
int main()
{
#ifdef debug
freopen("in.txt","r",stdin);
#endif
int t,n,q;
int op,a,b;
cin>>t;
while(t--)
{
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%d",&A[i]);
build(1,1,n);
while(q--)
{
scanf("%d %d %d",&op,&a,&b);
if(op==1)
{
node ans=query(1,a,b);
printf("%d\n",ans.pos);
}
else
update(1,a,b);
}
}
return 0;
}


  我想通过这次比赛,大家应该更加清楚怎么的真实水平到底是什么样的。这场比赛不论是题目质量,还是比赛的客观性都比前面的几场都高,毕竟是原创题,很有针对性。希望大家下来以后还好总结暑假培训期间的得失。