树的计数
(传送门)
题意
给定一颗有根树遍历的DFS序和BFS序,求符合条件的树的平均深度。
分析
所求的树有很多种的原因是:对于节点A、B在两种序中都相邻时,A既可以做B的兄弟,又可以做B的父亲。而这样除去A,B的关系外,对于其他任何节点关系都没有影响。所以这中情况对答案的贡献是0.5。对于A,B,如果A只能是B的父亲(也就是BFS序列中的断层),那么对答案的贡献为1。只要找这两种关系,将贡献值累加。
先把bfs序换成1..n,假设A、B 为i、i+1,它们的dfs&bfs序必须相邻且位置先后一样(dfs[i]+1==dfs[i+1]),要满足:1.dfs序<i+1的所有点的bfs序都要<bfs[i+1]
即是dfs时在i+1点的前面点的深度不能超过i+1的深度
2.假设点j为必须断层的点的前一点(该层最后一点),于满足dfs[i+1]<dfs[x]<dfs[j] 的所有x 必须满足 bfs[x]>=bfs[i+1],即可能与i+1同层的点必须是i+1的兄弟,如果j不是i+1的兄弟 j的老爸的bfs序肯定<bfs[i+1]。
每个点都要找到条件2的断层点 就有可能导致n^2的复杂度
可以开个临时的变量累加所有的0.5,如果在断层前遇到某个i和i+1不是兄弟,把临时变量清0,遇到断层时ans+=临时变量。判断条件2的时可用线段树优化
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=200001;
int n,dfs[MAXN],bfs[MAXN],rk[MAXN],MAX[MAXN],tree[MAXN*4];
double ans,save;
void build(int l,int r,int rt)
{
if(l==r)
{
tree[rt]=dfs[l];
return;
}
int mid=(l+r)/2;
build(l,mid,rt*2);
build(mid+1,r,rt*2+1);
tree[rt]=min(tree[rt*2],tree[rt*2+1]);
}
int query(int l,int r,int rt,int x,int y)
{
if(x<=l && r<=y) return tree[rt];
int mid=(l+r)/2,res=n;
if(x<=mid) res=min(res,query(l,mid,rt*2,x,y));
if(mid<y) res=min(res,query(mid+1,r,rt*2+1,x,y));
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&dfs[i]);
for(int i=1;i<=n;i++)
scanf("%d",&bfs[i]);
for(int i=1;i<=n;i++)
rk[bfs[i]]=i;
for(int i=1;i<=n;i++)
dfs[i]=rk[dfs[i]];
for(int i=1;i<=n;i++)
rk[dfs[i]]=i;
for(int i=1;i<=n;i++)
MAX[i]=MAX[i-1]>dfs[i] ? MAX[i-1] : dfs[i];
build(1,n,1);
ans=1;
for(int i=1;i<n;i++)
{
if(i==1 || rk[i+1]<rk[i]) ans=ans+1+save;
else
if(rk[i+1]==rk[i]+1)
{
if(MAX[rk[i]]<=i+1) save=save+0.5;
}
else
if(query(1,n,1,rk[i],rk[i+1])<i) save=0;
}
ans+=save;
printf("%.3f\n",ans-0.001);
printf("%.3f\n",ans);
printf("%.3f\n",ans+0.001);
return 0;
}
向量内积
(传送门)
题意
两个d 维向量A=[a1,a2,...,ad]与B=[b1,b2,...,bd]的内积为其相对应维度的权值的乘积和(A,B)= ∑Ai*Bi(i=1 to d)
分析
这题一看到没有什么太好的思路,考虑从暴力算法开始一步步优化,拿更多的分。
1.枚举,暴力判断,复杂度O(d*n^2),50分
2.观察到,当k=2时,两个向量的内积等于所对应的二进制数与运算后1的个数。
100位的二进制数可以压20位,就是说将其分成5块分别计算,计算1的个数可以预处理。
复杂度O(5*n^2),60分
3. k=3时,不能确定矩阵C的样子了,因为有三种情况0,1,2,但1^2 mod 3=1,2^2 mod 3=1,所以让这个矩阵的元素都平方一下,矩阵C变成了除了对角线其他都是1。内积的平方拆开就变成了d^2维的向量的内积。生成矩阵不是很好,为0的话就没有用了,所以直接用了全都是1的矩阵来跑答案。看到大家都是这么做的,时间卡的紧,可能AC不了,但是会有很高的分数,对于正式比赛已经足够。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10,maxd=100+10;
int a[maxn][maxd],b[maxn],c[maxn],x[maxn],y[maxn];
int n,d,k;
void work2()
{
int s=0;
for(int i=1;i<=n;i++)
s=s^x[i];
for(int i=1;i<=n;i++)
c[i]=s^x[i]^(x[i]&y[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=d;j++)
b[j]=b[j]^(x[i]&a[i][j]);
for(int i=1;i<=d;i++)
x[i]=b[i];
for(int i=1;i<=d;i++)
b[i]=0;
for(int i=1;i<=d;i++)
for(int j=1;j<=n;j++)
b[j]=b[j]^(x[i]&a[j][i]);
for(int i=1;i<=n;i++)
if(b[i]!=c[i])
{
for(int j=1;j<=n;j++)
if(i!=j)
{
s=0;
for(int k=1;k<=d;k++)
s=s^(a[i][k]&a[j][k]);
if(s==0)
{
printf("%d %d\n",i,j);
return;
}
}
}
printf("-1 -1\n");
}
void work3()
{
int s=0;
for(int i=1;i<=n;i++)
if(y[i]>0) y[i]=1;
for(int i=1;i<=n;i++)
s+=x[i];
for(int i=1;i<=n;i++)
c[i]=(s-x[i]+x[i]*y[i])%3;
for(int i=1;i<=n;i++)
for(int j=1;j<=d;j++)
for(int k=1;k<=d;k++)
b[(j-1)*d+k]+=(x[i]*a[i][j]*a[i][k]);
for(int i=1;i<=d*d;i++)
{
x[i]=b[i]%3;
b[i]=0;
}
for(int i=1;i<=d;i++)
for(int j=1;j<=d;j++)
for(int k=1;k<=n;i++)
b[k]+=(x[(i-1)*d+j]*a[k][i]*a[k][j]);
for(int i=1;i<=n;i++)
b[i]=b[i]%3;
for(int i=1;i<=n;i++)
if(b[i]!=c[i])
{
for(int j=1;j<=n;j++)
if(i!=j)
{
s=0;
for(int k=1;k<=d;k++)
s=s+a[i][k]*a[j][k];
if(s%3==0)
{
printf("%d %d\n",i,j);
return;
}
}
}
printf("-1 -1\n");
}
int main()
{
cin>>n>>d>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=d;j++)
{
scanf("%d",&a[i][j]);
a[i][j]=a[i][j]%k;
}
for(int i=1;i<=n;i++)
x[i]=rand()%k;
for(int i=1;i<=n;i++)
for(int j=1;j<=d;j++)
y[i]=y[i]+a[i][j]*a[i][j];
for(int i=1;i<=n;i++)
y[i]=y[i]%k;
if(k=2) work2();
else work3();
return 0;
}
矩阵游戏
(传送门)
题意
F[1][1]=1,F[i][j]=a*F[i][j-1]+b(j!=1),F[i][1]=c*F[i-1][m]+d(i!=1)
求F[n][m] mod 1e9+7。
分析
很明显,构造递推矩阵,用矩阵快速幂来做,但是看到数据范围发现会超时。然而一般的快速幂都是二进制来做的,对于这道题可以将做法改为十进制下的快速幂,轻松解决。
代码
#include <bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int MAXN=1e6+5;
struct matrix
{
long long a[2][2];
int n,m;
matrix() { memset(a,0,sizeof(matrix)); }
void init0()
{
n=m=2;
a[0][0]=a[1][1]=1;
a[0][1]=a[1][0]=0;
}
void init1(int aa,int bb)
{
n=m=2;
a[0][0]=aa;
a[0][1]=bb;
a[1][1]=1;
}
void init2(int aa)
{
n=2,m=1;
a[0][0]=aa;
a[1][0]=1;
}
};
matrix operator * (matrix m1,matrix m2)
{
matrix ret;
ret.n=m1.n;
ret.m=m2.m;
if(ret.n==2 && ret.m==2)
{
if(m1.a[1][1]!=1 || m1.a[1][0]!=0 || m2.a[1][1]!=1 || m2.a[1][0]!=0) throw 1;
ret.a[0][0]=m1.a[0][0]*m2.a[0][0]%MOD;
ret.a[0][1]=(m1.a[0][0]*m2.a[0][1]%MOD+m1.a[0][1])%MOD;
ret.a[1][0]=0;
ret.a[1][1]=1;
return ret;
}
for(int i=0;i<m1.n;i++)
for(int j=0;j<m2.m;j++)
for(int k=0;k<m1.m;k++)
ret.a[i][j]=(ret.a[i][j]+m1.a[i][k]*m2.a[k][j]%MOD)%MOD;
return ret;
}
matrix POW(matrix a,string str,int len)
{
matrix t,l0,ret;
ret.init0();
l0.init0();
t=a;
for(int i=len-1;i>=0;i--)
{
for(int j=0;j<10;j++)
{
if(j==str[i]-'0') ret=ret*l0;
l0=l0*t;
}
t=l0;
l0.init0();
}
return ret;
}
int main()
{
string s1,s2;
int a,b,c,d;
cin>>s1>>s2;
scanf("%d%d%d%d",&a,&b,&c,&d);
a%=MOD;b%=MOD;c%=MOD;d%=MOD;
int l1=s1.size(),l2=s2.size();
int x;
x=l1-1;
s1[x]--;
while(s1[x]<'0')
{
s1[x]+=10;
s1[x-1]--;
x--;
}
x=l2-1;
s2[x]--;
while(s2[x]<'0')
{
s2[x]+=10;
s2[x-1]--;
x--;
}
matrix m1,r1,m2,r2,r3,m3,r4,m4;
matrix t1;
m1.init1(a,b);
m2.init1(c,d);
m4.init2(1);
r1=POW(m1,s2,l2);
r2=m2*r1;
r3=POW(r2,s1,l1);
r4=r1*r3*m4;
cout<<r4.a[0][0]<<endl;
return 0;
}
书法家
(传送门)
题意
n *m方格,左下角方格坐标为(1,1),右上角方格坐标为(m,n) 。每个方格有一个整数的幸运值。幸运度的大小恰好是所有被笔写到的方格的幸运值之和。要在上面写上 ‘N’,‘O’,‘I’三个字母(对于字母的样子的定义看原题这就不详细描述),求最大幸运度
分析
没什么难度的dp,状态,方程什么都容易看出,就是比较恶心,懒得写了贴别人代码
代码
#include <bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=150+5,maxm=500+5;
int f[maxn][maxn][3][2],map[maxm][maxn],sum[maxm][maxn],h[maxn],fn[maxm],fi[maxm],fo[maxm],n,m;
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&map[j][i]);
sum[j][i]=sum[j][i-1]+map[j][i];
}
for(int p=0;p<</span>3;p++)
for(int q=0;q<</span>2;q++)
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++)
f[i][j][p][q]=-INF;
int t,p,q,i2,j2;
f[0]=-INF;
for(int k=0;k<=m;k++)
{
fn[k]=f[k-1];
for(int i=0;i<=n;i++)
h[i]=-INF;
for(int i=1;i<=n;i++)
{
t=-INF;q=-INF;p=-INF;
for(int j=n;j>=i;j--)
{
f[i][j][0][k%2]=max(f[i][j][0][(k-1)%2],0)+sum[k][j]-sum[k][i-1];
if(k>1)
{
if(n-j>j-i) f[i][j][1][k%2]=max(t+sum[k][j]-sum[k][i-1],f[i][j][1][k%2]);
else f[i][j][1][k%2]=t+sum[k][j]-sum[k][i-1];
t=max(f[i][j][0][(k-1)%2],t);
}
if(k>2)
{
j2=n+i-j;
h[j2]=max(h[j2],f[i][j2][1][(k-1)%2]);
q=max(q,h[j2]);q=max(q,h[i-1]);
if(n-j2>j2-i) f[i][j2][1][k%2]=q+sum[k][j2]-sum[k][i-1];
else f[i][j2][1][k%2]=max(f[i][j2][1][k%2],q+sum[k][j2]-sum[k][i-1]);
i2=n+1-i;j2=i2-n+j;
if(j2<</span>i2)
{
f[j2][i2][2][k%2]=max(f[j2][i2][2][(k-1)%2],p)+sum[k][i2]-sum[k][j2-1];
fn[k]=max(fn[k],f[j2][i2][2][k%2]);
}
p=max(f[j2][i2][1][(k-1)%2],p);
}
}
}
}
for(int p=0;p<</span>3;p++)
for(int q=0;q<</span>2;q++)
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++)
f[i][j][p][q]=-INF;
fo[4]=-INF;
for(int k=5;k<=m;k++)
{
fo[k]=fo[k-1];
for(int i=1;i<=n;i++)
for(int j=i+2;j<=n;j++)
{
f[i][j][0][k%2]=sum[k][j]-sum[k][i-1]+fn[k-2];
if(k>5) f[i][j][1][k%2]=map[k][i]+map[k][j]+max(f[i][j][0][(k-1)%2],f[i][j][1][(k-1)%2]);
if(k>6)
{
f[i][j][2][k%2]=sum[k][j]-sum[k][i-1]+f[i][j][1][(k-1)%2];
fo[k]=max(fo[k],f[i][j][2][k%2]);}
}
}
fi[8]=-INF;
for(int p=0;p<</span>3;p++)
for(int q=0;q<</span>2;q++)
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++)
f[i][j][p][q]=-INF;
for(int k=9;k<=m;k++)
{
fi[k]=fi[k-1];
for(int i=1;i<=n;i++)
for(int j=i+2;j<=n;j++)
{
f[i][j][0][k%2]=map[k][i]+map[k][j]+max(f[i][j][0][(k-1)%2],fo[k-2]);
if(k>9) f[i][j][1][k%2]=max(f[i][j][1][(k-1)%2],f[i][j][0][(k-1)%2])+sum[k][j]-sum[k][i-1];
if(k>10) f[i][j][2][k%2]=max(f[i][j][1][(k-1)%2],f[i][j][2][(k-1)%2])+map[k][j]+map[k][i];
fi[k]=max(fi[k],f[i][j][2][k%2]);
}
}
printf("%d",fi[m]);
return 0;
}
快餐店
(传送门)
题意
n条边连接n个点,找出一个点(点可以在图的任意位置),离最远的点最近
分析
显然该图是有环的,枚举删环上的每条边,剩下的就是一棵树。接下来要求这颗树的最长链。
这样是O(n^2)的,可以用线段树降到O(nlogn)。
对于环上的每个点i的子树,以i为起点的最长链长度为dis[i]。环上的边权用前缀和sum[]存。这样最长链就是max{sum[j]-sum[i]+dis[i]+dis[j]},用两颗线段树分别维护max{sum[j]+dis[j]}和max{dis[i]-sum[i]},然后更新答案。注意i≠j,且要用每颗子树的最长链更新答案。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
const long long INF=10000000000000000LL;
int to[maxn],next[maxn],head[maxn],tot=0;
int iscir[maxn],q[maxn],fa[maxn],cir[maxn],vis[maxn],cc,flag,n;
long long w[maxn],lt[maxn],rt[maxn],sum[maxn],d[maxn],dt[maxn],cirsum=0LL,ans=0LL,Ans=INF;
struct SegmentTrtot
{
#define ls (x<<1)
#define rs (x<<1|1)
#define maxt 500400
long long seg[maxt],seg2[maxt];
int mx1[maxt],mx2[maxt];
void pushup(int x)
{
if(seg[ls]>seg[rs])
{
seg[x]=seg[ls];
mx1[x]=mx1[ls];
seg2[x]=seg[rs];
mx2[x]=mx1[rs];
}
else
{
seg[x]=seg[rs];
mx1[x]=mx1[rs];
seg2[x]=seg[ls];
mx2[x]=mx1[ls];
}
if(seg2[ls]>seg2[x])
{
seg2[x]=seg2[ls];
mx2[x]=mx2[ls];
}
if(seg2[rs]>seg2[x])
{
seg2[x]=seg2[rs];
mx2[x]=mx2[rs];
}
}
void build(int l,int r,int x)
{
if(l==r)
{
seg[x]=sum[l]+dt[l];
seg2[x]=0LL;
mx1[x]=l;
mx2[x]=0;
return;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
pushup(x);
}
void update(int p,long long v,int l,int r,int x)
{
if(l==r && p==l)
{
seg[x]=v;
return;
}
int mid=(l+r)>>1;
if(p<=mid) update(p,v,l,mid,ls);
else update(p,v,mid+1,r,rs);
pushup(x);
}
void init()
{
memset(seg,0,sizeof(seg));
memset(seg2,0,sizeof(seg2));
memset(mx1,0,sizeof(mx1));
memset(mx2,0,sizeof(mx2));
}
}T;
struct SegmentTrtot2
{
#define ls (x<<1)
#define rs (x<<1|1)
#define maxt 500400
long long seg[maxt],seg2[maxt];
int mx1[maxt],mx2[maxt];
void pushup(int x)
{
if(seg[ls]>seg[rs])
{
seg[x]=seg[ls];
mx1[x]=mx1[ls];
seg2[x]=seg[rs];
mx2[x]=mx1[rs];
}
else
{
seg[x]=seg[rs];
mx1[x]=mx1[rs];
seg2[x]=seg[ls];
mx2[x]=mx1[ls];
}
if(seg2[ls]>seg2[x])
{
seg2[x]=seg2[ls];
mx2[x]=mx2[ls];
}
if(seg2[rs]>seg2[x])
{
seg2[x]=seg2[rs];
mx2[x]=mx2[rs];
}
}
void build(int l,int r,int x)
{
if(l==r)
{
seg[x]=dt[l]-sum[l];
mx1[x]=l;
mx2[x]=0;
return;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
pushup(x);
}
void update(int p,long long v,int l,int r,int x)
{
if(l==r && p==l)
{
seg[x]=v;
return;
}
int mid=(l+r)>>1;
if(p<=mid) update(p,v,l,mid,ls);
else update(p,v,mid+1,r,rs);
pushup(x);
}
void init()
{
for(int i=0;i<=500000;i++)
seg[i]=seg2[i]=-INF;
memset(mx1,0,sizeof(mx1));
memset(mx2,0,sizeof(mx2));
}
}Q;
void addEdge(int x,int y,long long z)
{
to[tot]=y; w[tot]=z; next[tot]=head[x]; head[x]=tot++;
to[tot]=x; w[tot]=z; next[tot]=head[y]; head[y]=tot++;
}
void dfs(int u,int f)
{
if(flag) return;
fa[u]=f;
vis[u]=1;
for(int i=head[u];~i;i=next[i])
{
if(to[i]==f) continue;
if(vis[to[i]])
{
if(!cir[1])
{
fa[to[i]]=u;
cir[1]=to[i];
flag=1;
}
return;
}else dfs(to[i],u);
}
}
void findcircle()
{
flag=cir[cc=1]=0;
dfs(1,0);
int k=cir[1];
while(fa[k]!=cir[1])
{
cir[++cc]=fa[k];
k=fa[k];
}
memset(iscir,0,sizeof(iscir));
for(int i=1;i<=cc;i++)
{
iscir[cir[i]]=1;
for(int j=head[cir[i]];~j;j=next[j])
{
if(to[j]==cir[i%cc+1])
{
lt[i]=rt[i%cc+1]=w[j];
cirsum+=lt[i];
break;
}
}
}
sum[1]=0LL;
for(int i=2;i<=cc;i++)
sum[i]=sum[i-1]+rt[i];
}
void solvetrtot(int o)
{
int s=0,e=1,pos=0,rt=cir[o];
q[0]=rt;
d[rt]=0;
while(s<e)
{
int u=q[s++];
vis[u]=2*o;
for(int i=head[u];~i;i=next[i])
{
if(iscir[to[i]]==1 || vis[to[i]]==2*o) continue;
d[q[e++]=to[i]]=d[u]+w[i];
if(d[to[i]]>d[pos]) pos=to[i];
}
}
s=0;e=1;
dt[o]=d[pos];
q[0]=pos;
d[pos]=0;
iscir[rt]=0;
long long an=0LL;
while(s<e)
{
int u=q[s++];
vis[u]=2*o+1;
for(int i=head[u];~i;i=next[i])
{
if(iscir[to[i]]==1 || vis[to[i]]==2*o+1) continue;
d[q[e++]=to[i]]=d[u]+w[i];
if(d[to[i]]>an) an=d[to[i]];
}
}
ans=max(ans,an);
iscir[rt]=1;
}
int main()
{
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x,y;
long long z;
scanf("%d%d%lld",&x,&y,&z);
addEdge(x,y,z);
}
findcircle();
memset(vis,0,sizeof(vis));
for(int i=1;i<=cc;i++)
solvetrtot(i);
T.init();
T.build(1,cc,1);
Q.init();
Q.build(1,cc,1);
for(int i=1;i<=cc;i++)
{
if(T.mx1[1]!=Q.mx1[1]) Ans=min(Ans,T.seg[1]+Q.seg[1]);
else Ans=min(Ans,max(T.seg[1]+Q.seg2[1],T.seg2[1]+Q.seg[1]));
sum[i]=rt[i]+sum[(i>1)?(i-1):cc];
T.update(i,dt[i]+sum[i],1,cc,1);
Q.update(i,dt[i]-sum[i],1,cc,1);
}
printf("%.1lf\n",(double)max(Ans,ans)/(double)2.0);
return 0;
}