良好的心态是成功的一半。
------xinyue
Day 0
到这个时候,做题确实也是不顶什么用的了,CQ今年省选给的考纲特别窄,特别妖,什么FFT,莫比乌斯,LCT都没给(还好没给)。和noip考前一样,我们机房8人上南山试机。键盘鼠标都还行,左边八中的,右边一中的,都不熟……,想着自己练Spaly练得少,就敲了个模板机械臂,当时没敲对,里面气氛又很闷,心头很慌,不敢再敲了。考前最重要的是心态,这个小失败确实是让人很沮丧的......早早就睡了,期望忘掉这些烦恼。因为模拟考试不理想的原因,我想了很久滚粗这个事了
Day1
和上次进考场一样,8个人带教练一起在门口喊出“AK”的口号(有梦想就有奇迹),被隔壁学校以6666的回复嘲讽.........,进考场,分配到了一个大面包,一个小面包,一盒牛奶(没看到什么人吃)。T1很明显是抄的ZJOI2011的最小割,变了一下询问,裸的模板,练过几遍,被(850,8500)的数据范围吓到了点,写的时候特别注意了省去没必要的边和用数组预处理+前向星跑。
T2求平面上k远点对,一开始很懵逼,我连次远点对都不会求,果断先跳过。T3是很裸的数位动规。最后我回来把T2写了,因为用了double调了40min+的精度,还好最后弄完了。题解一起放下面。
T1 模板就不说了
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=865,M=100005;
const int INF=0x7fffffff/2;
struct node{int to,next,flow,anc;}q[M];
int h[N],cnt=1,dis[N],gap[N],prt[N];
int f[N][N],map[N][N],tmp[810000],top=0,n,m,S,T;
bool vst[N];
void qxx(int x,int y,int flow)
{
cnt++;q[cnt].to=y;q[cnt].next=h[x];q[cnt].anc=flow;h[x]=cnt;
cnt++;q[cnt].to=x;q[cnt].next=h[y];q[cnt].anc=flow;h[y]=cnt;
}
void Init()
{
scanf("%d%d",&n,&m);
int i,j,x,y,k;
memset(f,0,sizeof(f));
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&k);
f[x][y]+=k;f[y][x]+=k;
}
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
if(f[i][j])qxx(i,j,f[i][j]);
}
int SAP(int x,int maxf)
{
if(x==T)return maxf;
int i,y,ret=0,dlt;
for(i=h[x];i;i=q[i].next)
{
y=q[i].to;
if(dis[x]==dis[y]+1&&q[i].flow>0)
{
dlt=SAP(y,min(maxf,q[i].flow));
q[i].flow-=dlt;
q[i^1].flow+=dlt;
maxf-=dlt;
ret+=dlt;
if(!maxf||dis[S]>=n)return ret;
}
}
if(!(--gap[dis[x]]))dis[S]=n;
gap[++dis[x]]++;
return ret;
}
void DFS(int x)
{
vst[x]=1;
int i,y;
for(i=h[x];i;i=q[i].next)
{
y=q[i].to;
if(!vst[y]&&q[i].flow>0)DFS(y);
}
}
void Gusfiled()
{
int i,j,ans;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)map[i][j]=INF;
for(i=2;i<=n;i++)prt[i]=1;
for(i=2;i<=n;i++)
{
for(j=2;j<=cnt;j++)q[j].flow=q[j].anc;
for(j=0;j<=N-3;j++)dis[j]=gap[j]=vst[j]=0;
S=prt[i];T=i;
ans=0;
while(dis[S]<=n)ans+=SAP(S,INF);
DFS(S);
for(j=i+1;j<=n;j++)
if(prt[j]==prt[i]&&!vst[j])prt[j]=i;
for(j=1;j<=i-1;j++)
map[i][j]=map[j][i]=min(ans,map[prt[i]][j]);
}
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)tmp[++top]=map[i][j];
sort(tmp+1,tmp+top+1);
tmp[0]=-1;
int Add=0;
for(i=1;i<=top;i++)
if(tmp[i]!=tmp[i-1])Add++;
printf("%d",Add);
}
int main()
{
Init();
Gusfiled();
return 0;
}
T2 大意:求平面上k远点对
很有意思的一道题,凸包+旋转卡壳。
只会写平面上最远点对,现在要求平面上k远点对,那么总思路就是把k远点对转成最远点对。
当求到现在的最远点对(point1,point2)后,若不加处理,下次若继续求最远点对,一定还是(point1,point2),这不是我们想要的,故,要删除这个点对。删后再求最远点对,那么就是次远点对了,以此处理,最终会求得k远点对。那么问题就是删除操作,一个点对由两个点组成,任意删除一个点,这个点对将不存在,我们现在要考虑删除哪个点,我使用的极角排序,正常时候,每次凸包+卡壳的时间其实是O(nlogn+n),logn 是极角排序所带来的,从范围来看,这道题O(kn)是最合理的,显然,每次求最远点对,我不能极角排序,所以,我们要固定编号为1的点(我的代码里是y坐标最小的点中x坐标最小的)。那么删除的时候,就删除标号大的一个点,即为point2。删点的时候,考虑将会带来哪些影响,显然,图中现在存在的点和point2的距离将因为删点永远不会被后面计算到,那么我们就暴力枚举图中剩下的点,求出他们与point2的距离,保存下来,这样,就可以放心地删掉这个点了。最后的部分就是对于那些保存下来的点对距离的处理。每一次删点,理论上最多会有n个点对距离需要保存,设一个Ans[]数组,Ans[i]保存的是前面的计算过程中,第i远点对距离,所以在要保留点对的时候,如果这个距离dis小于当前的Ans[k]就没有必要保存了,每次弄完,把Ans[]排一遍,保存最大的k个,如果某一次没有任何点对距离大于Ans[k],那么break,因为当前平面上最远点已经无法更新Ans[]了,Ans[k]即为所求。代码如下。
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
const double Eps=1e-10;
const LL N=200005LL,INF=100000000000000000LL;
struct point{LL x,y,i;double ag;}a[N],p[N];
LL n,m,Ans[N];
bool vst[N];
LL Dis(point p1,point p2)
{
LL x=p1.x-p2.x,y=p1.y-p2.y;
return x*x+y*y;
}
LL Cross(point p1,point p2,point p3)
{
return (p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y);
}
bool cmp(const point p1,const point p2)
{
if(fabs(p1.ag-p2.ag)<Eps)return Dis(a[1],p1)<Dis(a[1],p2);
return p1.ag<p2.ag;
}
void Init()
{
scanf("%lld%lld",&n,&m);
LL i,j,k;
for(i=1;i<=n;i++)scanf("%lld%lld",&a[i].x,&a[i].y);
LL Minx=INF,Miny=INF;
for(i=1;i<=n;i++)
if(a[i].y<Miny||(a[i].y==Miny&&a[i].x<Minx))
{
Miny=a[i].y;Minx=a[i].x;k=i;
}
swap(a[1],a[k]);
for(i=2;i<=n;i++)a[i].ag=atan2(a[i].y-a[1].y,a[i].x-a[1].x);
sort(a+2,a+n+1,cmp);
for(i=1;i<=n;i++)a[i].i=i;
}
bool cmp2(const LL x,const LL y){return x>y;}
void Solve()
{
LL ti,i,j,k,x,y,top,num=0,dis;
Ans[0]=-1;
for(ti=1;ti<=m;ti++)
{
top=0;
for(i=1;i<=n;i++)
{
if(vst[a[i].i])continue;
while(top>=2&&Cross(p[top],a[i],p[top-1])<=0)top--;
p[++top]=a[i];
}
dis=0;
p[0]=p[top];p[top+1]=p[1];
j=1;
for(i=1;i<=top;i++)
{
while(Dis(p[i],p[j])<=Dis(p[i],p[(j+1)%top]))j=(j+1)%top;
if(Dis(p[i],p[j])>dis)
{
dis=Dis(p[i],p[j]);
x=p[i].i;y=p[j].i;
}
}
if(x>y)swap(x,y);
vst[y]=1;
k=0;
for(i=1;i<=n;i++)
{
if(vst[i])continue;
if(Dis(a[y],a[i])>Ans[num])
{
k++;
Ans[num+k]=Dis(a[y],a[i]);
}
}
if(!k)break;
sort(Ans+1,Ans+num+k+1,cmp2);
num=min(m,num+k);
}
printf("%lld",Ans[m]);
}
int main()
{
Init();
Solve();
return 0;
}
T3 大意:求区间[L,R]内不同时出现4,8且有三个连续相同的数的个数
用记搜版的DP方便一些,dp[pos][Las2][Las1][bj4][bj8][flag][jud][sta]表示当前枚举到第pos位,pos+1位是Las1,pos+2位是Las2
(若无则为-1),bj4,bj8为前面是否有4,8两个数,flag为前面的数是否抵满了上界,jud为前面是否已经满足有三个连续相同的数,sta表示前面是否有数(判前导0用的)。代码如下。
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
LL f[15],len=0;
LL dp[15][12][12][2][2][2][2];
LL SPE(LL x,LL y,LL z)
{
if(x==-1||y==-1||z==-1)return 0;
if(x==y&&y==z)return 1;
return 0;
}
LL DFS(LL pos,LL Las2,LL Las1,LL bj4,LL bj8,LL flag,LL jud,LL sta)
{
if(!pos)
{
if(bj4&&bj8)return 0;
return jud;
}
if(!flag&&Las2!=-1&&Las1!=-1)
if(dp[pos][Las2][Las1][bj4][bj8][jud][sta]!=-1)
return dp[pos][Las2][Las1][bj4][bj8][jud][sta];
LL i,j,spe,ed=9,add=0;
if(flag)ed=f[pos];
for(i=0;i<=ed;i++)
{
if(!sta&&!i)j=-1;else j=i;
spe=SPE(Las2,Las1,j);
add+=DFS(pos-1,Las1,j,bj4||(i==4),bj8||(i==8),flag&&(i==ed),jud||spe,sta||i);
}
if(!flag&&Las2!=-1&&Las1!=-1)
dp[pos][Las2][Las1][bj4][bj8][jud][sta]=add;
return add;
}
LL solve(LL n)
{
len=0;
while(n){f[++len]=n%10;n/=10;}
return DFS(len,-1,-1,0,0,1,0,0);
}
int main()
{
memset(dp,-1,sizeof(dp));
LL n,m,ans=0;
scanf("%lld%lld",&n,&m);
ans-=solve(n-1);ans+=solve(m);
printf("%lld",ans);
return 0;
}
很幸运,Day1成功AK!
Day2
上山,等待,相互加油,这些操作都是差不多的。进去过后,T1是道数学题(模板题),一个Pollard Rho的分解,和你想装逼就可以加上的Miller_Rabin+Witnesse二次探测。第二题我坚信是考语文,这描述一绕一绕的,估计看的时候都懵逼了一阵子,读懂了挺简单的,就是一个01的可持久trie。T3一看就有点小麻烦(从没想过暴搜),在中途写错了3次的情况下,最后10min把程序调了出来,因为是800000的数据,我开不下,开的650000,我以为过不了的,就贴了份暴力,啊啊啊啊啊啊啊啊!30分啊,AK伏笔啊,不手抽贴那个暴力多好啊。。。。。。。。(&*^&*#!@*#^*!@#) 题解代码见下。
T1大意:解码
很少见的按要求写代码。。。。。说什么做什么就好了,其实很多条件不用给,可以用离散对数自己推出r=(p1-1)*(p2-1)和ed=1(mod r) r 即φ(N),这里就不说了。代码如下。
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
LL C,E,N,num=0,g[15];
LL gcd(LL x,LL y)
{
if(!y)return x;
return gcd(y,x%y);
}
LL Exgcd(LL a,LL b,LL &x,LL &y)
{
if(!b){x=1;y=0;return a;}
else
{
LL tmp=Exgcd(b,a%b,x,y);
LL t=x;x=y;y=t-a/b*y;
return tmp;
}
}
LL exgcd(LL a,LL b,LL c)
{
LL d,x,y;
d=Exgcd(a,b,x,y);
if(c%d)return -1;
x=(x*(c/d)%(b/d)+(b/d))%(b/d);
return x;
}
LL Mul(LL a,LL b,LL p)
{
LL ans=0;
while(b)
{
if(b&1)ans=(ans+a)%p;
a=(a+a)%p;
b>>=1;
}
return ans;
}
LL Ksm(LL a,LL b,LL p)
{
LL ans=1;
while(b)
{
if(b&1)ans=Mul(ans,a,p);
a=Mul(a,a,p);
b>>=1;
}
return ans;
}
LL Pollard(LL n,LL c)
{
LL i=1,k=2,x,y,d;
x=rand()%(n-1)+1;
y=x;
while(1)
{
i++;
x=(Mul(x,x,n)+c)%n;
d=gcd(y-x,n);
if(1<d&&d<n)return d;
if(x==y)return n;
if(i==k){y=x;k<<=1;}
}
}
void Find(LL n,LL k)
{
if(n==1)return;
if(n!=N){g[++num]=n;return;}
LL p=n;
while(p>=n)p=Pollard(p,k--);
Find(p,k);
Find(n/p,k);
}
void Solve()
{
LL R,M,D;
num=0;
Find(N,10007);
R=(g[1]-1)*(g[2]-1);
D=exgcd(E,R,1);
M=Ksm(C,D,N);
printf("%lld %lld",D,M);
}
int main()
{
srand(19990510);
scanf("%lld%lld%lld",&E,&N,&C);
Solve();
return 0;
}
/*
3 187 45
*/
T2 大意:很复杂的匹配..........
从区间[L,R]的限制上不难想到可持久化,添加一个字符串的时候,只有在这个串的结束位置,将它的size令为前缀与其匹配的,最长的串的size+1,这样一个点的size即为第一串到当前串,匹配表项的改变次数。当询问的时候,其实掩码就相当于32,利用可持久化,做区间中减法,同理,都是找最长的串的size作为全值,即为所求。代码如下。
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=34000005,M=2000005;
int tr[N][2],sum[N],root[M],sign=0;
int Numb[10],jilu[40],tmp[40],len;
char s[40];
void Get()
{
int i,j,k=0;
memset(jilu,0,sizeof(jilu));
memset(tmp,0,sizeof(tmp));
for(i=4;i>=1;i--)
{
j=0;
while(Numb[i])
{
j++;
jilu[k+j]=Numb[i]%2;
Numb[i]/=2;
}
k+=8;
}
for(i=k;i>=k-len+1;i--)tmp[k-i+1]=jilu[i];
}
int Insert(int rt,int pos,int val)
{
int i,x;
x=++sign;
for(i=0;i<=1;i++)tr[x][i]=tr[rt][i];sum[x]=sum[rt];
if(pos>len){sum[x]=val+1;return x;}
int k=tmp[pos];
if(sum[tr[rt][k]])val=sum[tr[rt][k]];
tr[x][k]=Insert(tr[rt][k],pos+1,val);
return x;
}
int Query(int rt1,int rt2,int pos,int val1,int val2)
{
if(pos>len)return val1-val2;
int k=tmp[pos];
if(sum[tr[rt1][k]])val1=sum[tr[rt1][k]];
if(sum[tr[rt2][k]])val2=sum[tr[rt2][k]];
return Query(tr[rt1][k],tr[rt2][k],pos+1,val1,val2);
}
int main()
{
int n=0,i,j,Q,x,y;
scanf("%d",&Q);
for(i=1;i<=Q;i++)
{
scanf("%s",s);
if(s[0]=='A')
{
scanf("%d.%d.%d.%d/%d",&Numb[1],&Numb[2],&Numb[3],&Numb[4],&len);
Get();
n++;root[n]=Insert(root[n-1],1,0);
}
else
{
scanf("%d.%d.%d.%d",&Numb[1],&Numb[2],&Numb[3],&Numb[4]);
scanf("%d%d",&x,&y);
len=32;Get();
printf("%d\n",Query(root[y],root[x-1],1,0,0));
}
}
return 0;
}
T3 大意:(不好描述,看题目吧)
时间复杂度,一定是要依托于k来计算,看似要枚举的东西很乱,细细的规划一下,可以想到把他们以一个二元组区分(p,size)
表示一个数的最大素数为p,且为size个素数相乘,一开始预处理,就可以求出2-127这31个质数的最大size,即 pi^size[i]>=N(size[i]取到最小)(这样能保证之后的所有状态的权值都是小于N的),一开始,我们的状态数就只有sigma(size[i]) ,1<=i<=31(表示第i个质数) ,即一开始时,每一个二元组只有一个有效状态,(该二元组的其他所有状态一定没有当前状态值优),且这些状态表示的序列都为 size 个 p。该状态的权值即为pi^size[i]。因为要频繁询问当前最大权值和具有修改操作,我选择了可并堆。每一次,取出可并堆的堆顶元素,即为最大权值,表示我现在选择了它,可以找到它原来的那个序列,那么现在就要利用这个序列扩充新的序列。设size为这个二元组的size,p为这个二元组的p,那么相当于是要找到size个素数相乘,且最大素数为p。规定要用最小表示法,即这size个素数单调不下降组成一个序列,这样,我们就要枚举将其中的哪一个素数变小,它变化后不能破坏这个序列单调性,在枚举完后,会有一个新的权值(可以O(1)计算得),它可能已经在前面搜索中进堆了(举例来说,对于一个二元组(7,5),最初始为7*7*7*7*7,它接下来会搜索到序列(不是直接枚举到) 5*5*7*7*7 (记为f1)和3*7*7*7*7(记为f2) 显然f1会f2先出堆,f1 会搜到序列 3*5*7*7*7(记为f3),那么f3已经入堆了,之后如果f2出堆了,那么它也会枚举到f3,但此时f3已经不能入堆,否则将有一些数被算重),因此这里加一个哈希表来判。以此处理,每次出堆会算出当前最大值,且最多会多进入60个新的序列(其实远远达不到),那么时间复杂度就是多项式,O(k*logm)m为可并堆里面的元素个数。数组可能会吃紧,考虑到一个序列出堆后就没用了,可以写个回收站来回收空间以图再利用(可并堆里的元素也是如此)。这样虽然代码长了点,也能很好的解决这个问题了(ORZ%%%%%搜索+减枝过的),可能叙述有些难懂,代码还是很好理解的。(代码里用i表示第i个素数pi)
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL N=650005,mod=1000003;
LL prime[200],num=0,size[200],m,NNN;
LL jilu[N],Add=0,He=0,SPE=0;
LL tmp[N]={0},top=0;
struct data{LL size,i;}P[2500];LL cnt=0;
LL map[N][62];
struct node{LL L,R,val,i;}tr[N];LL sign=0,root;
bool vst[200];
struct Haxi
{
LL h[1000005],tot;
struct qxx{LL to,next;}q[8000005];
LL Ask(LL val)
{
LL i,x=val%mod;
for(i=h[x];i;i=q[i].next)
if(q[i].to==val)return 1;
return -1;
}
LL Insert(LL val)
{
LL i,x=val%mod;
tot++;
q[tot].to=val;q[tot].next=h[x];h[x]=tot;
}
}pq;
LL CNTNUM()
{
if(Add)return jilu[Add--];
return ++He;
}
LL CNTclean(LL x){jilu[++Add]=x;}
LL Numb()
{
if(top)return tmp[top--];
return ++sign;
}
void Clean(LL x)
{
tr[x].L=tr[x].R=tr[x].i=tr[x].val=0;
tmp[++top]=x;
}
void Build()
{
LL i,j,x;
for(i=2;i<=128;i++)
{
if(!vst[i])prime[++num]=i;
for(j=1;j<=num;j++)
{
if(i*prime[j]>128)break;
vst[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
LL n;
scanf("%lld%lld",&n,&m);
for(i=1;i<=num;i++)
{
x=n;j=0;
while(x>=prime[i]){x/=prime[i];j++;}
size[i]=j;
}
}
LL Ksm(LL a,LL b)
{
LL ans=1;
while(b)
{
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
LL Merge(LL x,LL y)
{
if(!x||!y)return x+y;
if(tr[x].val<tr[y].val)swap(x,y);
tr[x].R=Merge(tr[x].R,y);
swap(tr[x].L,tr[x].R);
return x;
}
void Pop()
{
LL x=root;
root=Merge(tr[root].L,tr[root].R);
Clean(x);
}
void Solve()
{
LL i,j,x,k;
pq.tot=0;
for(i=1;i<=num;i++)
for(j=1;j<=size[i];j++)
{
cnt=CNTNUM();P[cnt].size=j;P[cnt].i=i;
}
LL nowval;
root=0;
for(i=1;i<=He;i++)
{
nowval=Ksm(prime[P[i].i],P[i].size);
for(j=1;j<=P[i].size;j++)map[i][j]=P[i].i;map[i][0]=P[i].size;
x=Numb();
tr[x].L=tr[x].R=0;tr[x].i=i;tr[x].val=nowval;
pq.Insert(nowval);
root=Merge(root,x);
}
LL Ti,bj,Toval;
for(Ti=1;Ti<=m;Ti++)
{
i=tr[root].i;nowval=tr[root].val;
if(Ti==m){printf("%lld",nowval);break;}
Pop();
for(j=1;j<=map[i][0]-1;j++)
{
if(map[i][j]>1&&(map[i][j]>map[i][j-1]||j==1))
{
Toval=nowval;
Toval=Toval/prime[map[i][j]]*prime[map[i][j]-1];
if(pq.Ask(Toval)==-1)pq.Insert(Toval);
else continue;
cnt=CNTNUM();
for(k=0;k<=map[i][0];k++)map[cnt][k]=map[i][k];
map[cnt][j]--;
x=Numb();
tr[x].L=tr[x].R=0;tr[x].i=cnt;tr[x].val=Toval;
root=Merge(root,x);
}
}
CNTclean(i);
}
}
int main()
{
Build();
Solve();
return 0;
}
最终Day2小遗憾270 没能AK 被lgz成功
小结 300+270 省选的结束也算这一重庆阶段良好收官,再接再厉,希望能在接下来的考试中继续前行。