T1:
n
≤
6500
n\le6500
n≤6500
题解:
因为 1 0 i + 1 / 2 i & 1 = 0 10^{i+1}/2^i\&1=0 10i+1/2i&1=0, 1 0 i / 2 i & 1 = 1 10^i/2^i\&1=1 10i/2i&1=1,所以 1 0 k 10^k 10k的二进制表示的末尾一定有 k k k个0
所以可以记录长度为
l
e
n
len
len的所有合法串及其对应的二进制表示,然后考虑添加第
l
e
n
+
1
len+1
len+1位为0或1,当这个串的二进制表示的第
l
e
n
+
1
len+1
len+1位为1时就不用拓展了(肯定不合法)。所以由
l
e
n
→
l
e
n
+
1
len\rarr len+1
len→len+1,就可以推出第
n
n
n小的萌数。
由于需要高精度,每个串对时间的贡献为
d
2
d^2
d2,
d
d
d为答案的位数,总复杂度
O
(
n
d
2
)
O(nd^2)
O(nd2)
Code:
#include<bits/stdc++.h>
#define pb(x) push_back(x)
using namespace std;
struct Big{
int s[505],len;
Big(){memset(s,0,sizeof s),len=1;}
Big operator + (const Big &B)const{
Big c;c.len=max(len,B.len);
for(int i=0;i<c.len;i++)
if((c.s[i]+=s[i]+B.s[i])>1) c.s[i+1]++,c.s[i]&=1;
if(c.s[c.len]) c.len++;
return c;
}
Big operator * (int t){
Big c;c.len=len;
for(int i=0;i<len;i++)
if((c.s[i]+=s[i]*t)) c.s[i+1]+=c.s[i]>>1,c.s[i]&=1;
for(int &i=c.len;c.s[i];i++) c.s[i+1]+=c.s[i]>>1,c.s[i]&=1;
return c;
}
void write(){
for(int i=len-1;i>=0;i--) putchar(s[i]+'0');
putchar('\n');
}
}p10,p2;
vector<Big>f,tf,g,tg;
int n,m,cnt;
int main()
{
freopen("quiz.in","r",stdin);
freopen("quiz.out","w",stdout);
scanf("%d",&n),p10.s[0]=p2.s[0]=1;
f.pb(Big()),g.pb(Big());
for(int k=0;;k++,p10=p10*10,p2=p2*2){
m=f.size(),tf.clear(),tg.clear();
for(int i=0;i<m;i++) if(!g[i].s[k]) tf.pb(f[i]),tg.pb(g[i]);
for(int i=0;i<m;i++) if(!g[i].s[k]){
tf.pb(f[i]+p2),tg.pb(g[i]+p10),++cnt;
if(cnt==n) {tf.back().write();return 0;}
}
f.swap(tf),g.swap(tg);
}
}
T2:
n
≤
55000
n\le55000
n≤55000,询问数
≤
2
n
\le2n
≤2n
题解:
最短路问题。由于是多对点,显然什么优化建边行不通。
那么另外一个常见技巧就是分治了。
由于给出的图是一个多边形的三角剖分,因此每条对角线都能将这个多边形
分成两个部分,就可以采用分治的方法,点对分为跨过这个对角线或者没跨过对角线。
Code(我又手残把bfs的队列手写成栈了,惨遭爆5,还有注意询问的数量是小于等于2n):
#include<bits/stdc++.h>
#define maxn 55005
#define pb(x) push_back(x)
using namespace std;
int n,m,ans[maxn<<1],siz[maxn],d[2][maxn];//!!!maxn<<1
bool L[maxn],R[maxn];
struct edge{int x,y;};
struct qry{int x,y,id;};
vector<int>G[maxn];
int que[maxn],l,r;//!!!l,r
void BFS(int u,int *dis){
que[l=r=1]=u,dis[u]=0;
while(l<=r){
u=que[l++];
for(int i=0,lim=G[u].size(),v;i<lim;i++) if(dis[v=G[u][i]]==-1) dis[v]=dis[u]+1,que[++r]=v;
}
}
void solve(vector<int>&a,vector<edge>&e,vector<qry>&q){
if(q.empty()) return;
if(a.size()==3){
for(int i=0,lim=q.size();i<lim;i++) ans[q[i].id]=(q[i].x!=q[i].y);
return;
}
int u,v,mn=1e9,n=a.size(),m=e.size();
siz[a[0]]=1;
for(int i=1;i<n;i++) siz[a[i]]=siz[a[i-1]]+1;
for(int i=0;i<m;i++){
int x=e[i].x,y=e[i].y,len=siz[x]>siz[y]?siz[x]-siz[y]-1:siz[y]-siz[x]-1;
if((len=max(len,n-2-len))<mn) mn=len,u=x,v=y;
}
if(u>v) swap(u,v);
vector<int>a1,a2; vector<edge>e1,e2; vector<qry>q1,q2;
a1.clear(),a2.clear(),e1.clear(),e2.clear(),q1.clear(),q2.clear();
for(int i=0;i<n;i++){
if(u<=a[i]&&a[i]<=v) L[a[i]]=1,a1.pb(a[i]);
if(a[i]<=u||v<=a[i]) R[a[i]]=1,a2.pb(a[i]);
}
for(int i=0;i<m;i++){
int x=e[i].x,y=e[i].y;
if(L[x]&&L[y]) e1.pb(e[i]);
if(R[x]&&R[y]) e2.pb(e[i]);
}
for(int i=0,lim=q.size();i<lim;i++){
int x=q[i].x,y=q[i].y;
if(L[x]&&L[y]) q1.pb(q[i]);
if(R[x]&&R[y]) q2.pb(q[i]);
}
for(int i=0;i<n;i++) d[0][a[i]]=d[1][a[i]]=-1,G[a[i]].clear();
for(int i=0,lim=e.size();i<lim;i++) G[e[i].x].pb(e[i].y),G[e[i].y].pb(e[i].x);
BFS(u,d[0]),BFS(v,d[1]);
for(int i=0,lim=q.size();i<lim;i++){
int x=q[i].x,y=q[i].y;
ans[q[i].id]=min(ans[q[i].id],min(d[0][x]+d[0][y],d[1][x]+d[1][y]));
}
for(int i=0;i<n;i++) L[a[i]]=R[a[i]]=0;
solve(a1,e1,q1),solve(a2,e2,q2);
}
vector<int>a;
vector<edge>e;
vector<qry>q;
int main()
{
freopen("drive.in","r",stdin);
freopen("drive.out","w",stdout);
scanf("%d",&n); int x,y;
for(int i=1;i<=n;i++) a.pb(i),e.push_back((edge){i,i%n+1});
for(int i=1;i<=n-3;i++) scanf("%d%d",&x,&y),e.push_back((edge){x,y});
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),q.push_back((qry){x,y,i}),ans[i]=1e9;
solve(a,e,q);
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
T3:
n
≤
32000
n\le32000
n≤32000
题解:
看到随机树,就想到暴力爬链。
考虑如何统计某条边作为中位数的路径条数。
奇数个数里的中位数,就是小于它的和大于它的数量相等。
从小到大添加边,已经添加的设为
1
1
1,未添加的设为
−
1
-1
−1,求当前边作为中位数的路径条数时,先将其添加(改为
1
1
1),然后只需要求出树中包含这条边的权和为
1
1
1的路径数即可。
可以记录
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
i
i
i子树内到
i
i
i权和为
j
j
j的路径条数,设当前添加的边的端点为
x
x
x,只需从
x
x
x往上枚举路径的
l
c
a
lca
lca计算方案数(计算时还要枚举
x
x
x的子树内的权和),更新也很好更新,复杂度就是
O
(
∑
d
o
w
n
i
∗
u
p
i
)
O(\sum down_i*up_i)
O(∑downi∗upi),
d
o
w
n
i
down_i
downi表示
i
i
i向下的最大深度,
u
p
i
up_i
upi表示向上的长度,出题人说随机情况下为
O
(
n
n
)
O(n\sqrt n)
O(nn)。
Code:
#include<bits/stdc++.h>
#define maxn 32005
#define f(i,j) F[i][(j)+O]
using namespace std;
const int O = 300;
pair<int,int>a[maxn];
int n,cnt,fa[maxn],dep[maxn],F[maxn][605],c[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],tot;
inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
void dfs(int u,int ff){
fa[u]=ff,c[u]=-1;
for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff)
dfs(v,u),dep[u]=max(dep[u],dep[v]+1),a[++cnt]=make_pair(w[i],v);
}
int solve(int x){
c[x]=1;
for(int i=fa[x],k=0;i;k+=c[i],i=fa[i])
for(int j=-dep[x];j<=dep[x];j++) f(i,j+k-1)-=f(x,j),f(i,j+k+1)+=f(x,j);
int ret=0;
for(int i=x,k=c[x];fa[i];i=fa[i],k+=c[i])
for(int j=-dep[x];j<=dep[x];j++) ret+=(f(fa[i],1-(j+k))-f(i,1-(j+k)-c[i]))*f(x,j);
return ret;
}
int main()
{
freopen("draw.in","r",stdin);
freopen("draw.out","w",stdout);
scanf("%d",&n);
for(int i=1,x,y,z;i<n;i++) scanf("%d%d%d",&x,&y,&z),line(x,y,z),line(y,x,z);
dfs(1,0);
for(int i=1;i<=n;i++) for(int j=i,k=0;j;j=fa[j],k--) f(j,k)++;
sort(a+1,a+n);
long long ans=0;
for(int i=1;i<n;i++) ans+=1ll*a[i].first*solve(a[i].second);
printf("%lld\n",ans);
}