其实省选一轮过去已经有一段时间了,(蒟蒻山东rank 49,卡线到了二轮。。。)但因为先修课的原因,一直未能作总结。。。
DAY 1:这一天的暴力分75分,我拿到了55分。
T1的20分暴力比较好拿,正解是对每两个块之间的元素交换blabla后dfs,总之没听懂。
UPD:看了一下网上的题解,其实正解思路也还蛮简单,穷举不同2^i长度意义下的逆序对,如果超过两个,那么说明这个方法不可能成功。如果是一个或两个,那么分情况讨论后交换,继续dfs。同时需要一个蛮显然的结论,当一个操作序列长度为n时,无论怎样调换操作顺序,都是合法解,因此答案加上fac[n];code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct hp{
int kind,len;
int a[5000];
}opt[15];
int a[5000],ans=0,n;
int mi[15]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384};
int fac[15];
void swapi(int x,int y,int len)
{
int i;
for (i=0;i<len;++i)
swap(a[x+i],a[y+i]);
}
void dfs(int step,int now)
{
int i,j,top=0;
int stack[3]={0,0,0};
if (now==n+1)
{
ans+=fac[step-1];
return;
}
for (i=1;i<=mi[n];i+=opt[now+1].len)
{
if (a[i+opt[now].len-1]+1!=a[i+opt[now].len])
{
if (top==2) return;
stack[++top]=i;
}
}
if (top==0) {dfs(step,now+1); return;}
if (top==1)
{
i=stack[1];
if (a[i+opt[now+1].len-1]+1==a[i])
{
swapi(i,i+opt[now].len,opt[now].len);
dfs(step+1,now+1);
swapi(i,i+opt[now].len,opt[now].len);
}
return;
}
if (top==2)
{
i=stack[1]; j=stack[2];
if (a[i+opt[now].len-1]+1==a[j+opt[now].len]&&a[j+opt[now].len-1]+1==a[i+opt[now].len])
{
swapi(i,j,opt[now].len);
dfs(step+1,now+1);
swapi(i,j,opt[now].len);
swapi(i+opt[now].len,j+opt[now].len,opt[now].len);
dfs(step+1,now+1);
swapi(i+opt[now].len,j+opt[now].len,opt[now].len);
}
if (a[j+opt[now].len-1]+1==a[i]&&a[j+opt[now+1].len-1]+1==a[i+opt[now].len])
{
swapi(i,j+opt[now].len,opt[now].len);
dfs(step+1,now+1);
swapi(i,j+opt[now].len,opt[now].len);
}
if (a[i+opt[now].len-1]+1==a[j]&&a[i+opt[now+1].len-1]+1==a[j+opt[now].len])
{
swapi(j,i+opt[now].len,opt[now].len);
dfs(step+1,now+1);
swapi(j,i+opt[now].len,opt[now].len);
}
return;
}
}
int main()
{
int t,i,j;
scanf("%d",&n);
for (i=1;i<=mi[n];++i)
scanf("%d",&a[i]);
fac[0]=1;
for (i=1;i<=n;++i)
fac[i]=fac[i-1]*i;
for (i=1;i<=n;++i)
{
t=0;
for (j=1;j<=mi[n];j+=mi[i-1])
opt[i].a[++t]=j;
opt[i].kind=t;
opt[i].len=mi[i-1];
}
opt[n+1].len=mi[n];
dfs(1,1);
printf("%d\n",ans);
}
T2的35分暴力也还好,前20分乱搞即可,后15分因为是链状数据,在其上套一个线段树即可,但考试的时候未考虑是Win下评测,爆栈了两次。正解是虚树+欧拉路径+LCA,没听懂,不过zky学长的dfs序也蛮好理解的,每次处理出dfs序最左边的藏宝点与最右边的藏宝点,两者之间距离*2输出即可。维护需要处理出每个点的在dfs序中的最左最右位置。
T3的20分暴力考试没有想到,但其实是一个简单的DP,60%的数据需要用到矩阵乘法,100%的数据需要FFT。
Upd:夏令营时又一次听到了60%解法,终于明白了。DP方程比较简单 f[i][x]=f[i-1][x*ni[s[i]] 1<=i<=nums 然后我们可以套用快速幂的思想,发现可以在O(m^2)的是时间内由k推到2k,总的复杂度也就是O(logn*m^2),code:
#include<iostream>
#include<cstdio>
#include<cstring>
#define P 1004535809
using namespace std;
struct hp{
long long f[8001];
};
hp e,ans;
int n,m,x,nums;
hp cheng(hp a,hp b)
{
int i,j;
hp c;
for (i=0;i<=m-1;++i)
for (j=0;j<=m-1;++j)
c.f[(i*j)%m]=(c.f[(i*j)%m]+(a.f[i]*b.f[j])%P)%P;
return c;
}
hp work(int x)
{
hp t;
if (x==1)
return e;
t=work(x/2);
if (x%2==0) return cheng(t,t);
else return cheng(cheng(t,t),e);
}
int main()
{
int i,t;
scanf("%d%d%d%d",&n,&m,&x,&nums);
for (i=1;i<=nums;++i)
{
scanf("%d",&t);
e.f[t]=1;
}
ans=work(n);
printf("%lld\n",ans.f[x]);
}
DAY 2:这一天的暴力分90分,我只拿到了20分。
T1其实非常非常简单,二分加最大流即可,但是考试的时候一直在向费用流方向考虑,2h30min就浪费在了各种胡思乱想中。。。
code(PS:不知道为啥从0开始建点就WA(连样例都过不了),从1开始就AC):
#include<iostream>
#include<cstdio>
#include<cstring>
#define mid (l+r)/2
#define inf 0x7fffffffffffffffLL
using namespace std;
struct hp{
int u,v;
long long c;
}a[10000];
int e,ai[51],bi[51],n,m,next[10000],cur[200],pre[200],lev[200],gap[200],point[200];
long long l,r;
int pic[51][51];
long long sum=0;
void add(int x,int y,long long c)
{
e++; next[e]=point[x]; cur[x]=point[x]=e;
a[e].u=x; a[e].v=y; a[e].c=c;
e++; next[e]=point[y]; cur[y]=point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0;
}
long long ISAP(int vs,int vt)
{
int u,v,i,minl;
long long maxf=0,aug; bool f;
memset(pre,0,sizeof(pre));
memset(gap,0,sizeof(gap));
memset(lev,0,sizeof(lev));
gap[0]=vt-vs+1; u=vs;
while (lev[vs]<vt)
{
f=false;
for (v=cur[u];v!=0;v=next[v])
if (a[v].c>0&&lev[u]==lev[a[v].v]+1)
{cur[u]=v; f=true; break;}
if (f)
{
pre[a[v].v]=v;
u=a[v].v;
if (u==vt)
{
aug=inf;
for (i=v;i!=0;i=pre[a[i].u])
if (aug>a[i].c)
aug=a[i].c;
maxf+=aug;
for (i=v;i!=0;i=pre[a[i].u])
{
a[i].c-=aug;
a[i^1].c+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (i=point[u];i!=0;i=next[i])
if (a[i].c>0&&minl>lev[a[i].v])
minl=lev[a[i].v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
cur[u]=point[u];
gap[lev[u]]++;
if (u!=vs) u=a[pre[u]].u;
}
}
return maxf;
}
bool work(long long x)
{
int i,j;
memset(cur,0,sizeof(cur));
memset(point,0,sizeof(point));
memset(next,0,sizeof(next));
e=1;
for (i=1;i<=m;++i)
add(1,i+1,(long long)(bi[i])*x);
for (i=1;i<=m;++i)
for (j=1;j<=n;++j)
if (pic[i][j]==1)
add(i+1,m+j+1,(long long)(inf));
for (i=1;i<=n;++i)
add(m+i+1,m+n+2,(long long)(ai[i])*1000000);
if (ISAP(1,m+n+2)==sum*1000000)
return true;
else return false;
}
int main()
{
int i,j,minn=2100000000;
scanf("%d%d",&n,&m);
for (i=1;i<=n;++i)
{
scanf("%d",&ai[i]);
sum+=ai[i];
}
for (i=1;i<=m;++i)
{
scanf("%d",&bi[i]);
minn=min(minn,bi[i]);
}
for (i=1;i<=m;++i)
for (j=1;j<=n;++j)
scanf("%d",&pic[i][j]);
l=1; r=(long long)(sum/minn)*1000000;
while (l<r)
{
if (work(mid))
r=mid;
else
l=mid+1;
}
printf("%0.6f\n",l/(1000000*1.0));
}
T2的暴力分有50分,线性筛法+dfs一遍处理出每个质数的幂,从而处理出每个数的约束个数。但考试的时候考虑的不够周全,就连20分的暴力都写残了。。。正解是部分和+莫比乌斯反演,依旧没听懂。
T3的暴力分有40分,但莫名写残只有20分,正解貌似是线段树+分类讨论,稍稍有点听懂的感觉。。。。
总结:1、考试和平常练习的时候一定要注重几种不同算法的结合,不能只考虑单纯一种算法,省选难度的题不可能只用简单的一种算法或数据结构。
2、写好暴力,在发现有问题时一定要多想一想为什么,要想出治本的方法,不能随便一改过掉样例就跳过。
3、平常多做一些省选难度的题目,逐渐熟悉各种模型转化和建模思想。
4、合理分配做题时间,不要光在一道题上死磕。