USACO20FEB P
Delegation P
将树的 \(n-1\) 条边划分为若干链,问最大的 \(k\),使得这些链长度均 \(\ge k\)。
\(n\le 10^5\)。
先二分。
由于一个点到自己父亲只有一条边,肯定要传一条最长的上去。然后考虑当前子树:
-
对根,从最小的开始匹配,二分找到最小的满足的,找不到则不合法。
-
对其他,若发现有不合法的,先记录下来因为可以往上传,找到第二个则不合法。
时间复杂度 \(O(n\log^2 n)\)。
点击查看代码
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n;vector<int>e[N];
int f[N];bool flag;
void dfs(int u,int fa,int l){
if(flag)return;
multiset<int>m;
for(int v:e[u]){
if(v==fa)continue;
dfs(v,u,l);
m.insert(f[v]+1);
}
if((u==1&&(m.size()&1))||(u!=1&&!(m.size()&1)))m.insert(0);
bool cur=false;
while(!m.empty()){
auto it1=m.begin();
m.erase(it1);
auto it2=m.lower_bound(l-*it1);
if(u==1){
if(it2==m.end())return flag=true,void();
m.erase(it2);
}
else{
if(it2==m.end()){
if(cur)return flag=true,void();
cur=true,f[u]=*it1;
}
else m.erase(it2);
}
}
}
bool check(int x){
memset(f,0,sizeof(f)),flag=false;
dfs(1,0,x);
return !flag;
}
int main(){
n=read();
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
e[u].push_back(v),e[v].push_back(u);
}
int l=1,r=n,mid,ans=1;
while(l<=r){
mid=(l+r)>>1;
if(check(mid))ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
return 0;
}
Equilateral Triangles P
\(n\times n\) 的方阵中有若干点,两点的距离为它们的曼哈顿距离。问等边三角形的数量。
\(n\le 300\)。
曼哈顿距离意义下,每个正三角形 \(ABC\) 都存在一点外心 \(O\),使得 \(OA,OB\) 分别与 \(x,y\) 轴平行,且 \(OA=OB=OC\)。
长这样:
那么 \(C\) 在 \(AB\) 关于 \(O\) 的对称线段上,斜着做前缀和即可,旋转四次分别做。
防止算重有细节。时间复杂度 \(O(n^3)\)。
点击查看代码
#include<bits/stdc++.h>
#define N 905
#define V 900
#define _ 300
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,ans,s[N][N];
char c[N][N],tp[N][N];
void solve(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
tp[j+_][n-i+1+_]=c[i+_][j+_];
memset(s,0,sizeof(s)),memcpy(c,tp,sizeof(c));
for(int i=1;i<=V;i++)
for(int j=1;j<=i;j++)
s[j][i+1-j]=s[j-1][i+2-j]+(c[j][i+1-j]=='*');
for(int i=2;i<=V;i++)
for(int j=i;j<=V;j++)
s[j][i+V-j]=s[j-1][i+V+1-j]+(c[j][i+V-j]=='*');
for(int i=_+1;i<=_+n;i++)
for(int j=_+1;j<=_+n;j++)
for(int k=j-1;k>_&&i-j+k>_;k--){
if(c[i][k]=='.'||c[i-j+k][j]=='.')continue;
ans+=s[i+j-k][j]-s[i][2*j-k];
}
}
int main(){
n=read();
for(int i=_+1;i<=_+n;i++)
scanf("%s",c[i]+_+1);
for(int i=1;i<=4;i++)solve();
printf("%d\n",ans);
return 0;
}
Help Yourself P
\(n\) 条线段,问所有线段的 \(2^n\) 个子集中,区间覆盖后的连通块数量的 \(k\) 次方之和模 \(10^9+7\)。
\(n\le 10^5\),\(2\le k\le 10\)。所有 \(l_i\) 和 \(r_i\) 形成 \(1\) 到 \(2n\) 的一个排列。
考虑 \(k=1\) 的情况,就是 Help Yourself G。
将所有线段按左端点升序排序。考虑加入 \([l,r]\) 的答案的贡献。
记 \(f_i\) 表示右端点为 \(i\) 的线段对答案产生的贡献。
-
对于右端点在 \([1,l-1]\) 的,与 \([l,r]\) 无交,连通块数加一,将 \(f_1,\dots,f_{l-1}\) 加 \(1\) 后累加至 \(f_r\)。
-
对于右端点在 \([l,r-1]\) 的,与 \([l,r]\) 相交,连通块数不变,直接累加至 \(f_{r}\)。
-
对于右端点在 \([r+1,2n]\) 的,其包含 \([l,r]\),此时有效的右端点在这些线段上,不能用于 \(f_r\),应把贡献累积到 \(f_{r+1}\) 到 \(f_{2n}\),就是乘上 \(2\)。
写一个支持单点改、区间查的线段树即可。
\(k\ne 1\) 时遇到的问题是 \(f_1\) 到 \(f_{l-1}\) 要 \(+1\) 该怎么处理。
二项式定理,\(\displaystyle (x+1)^k=\sum_{i=0}^{k}{k\choose i}x^i\),分别维护 \(x^0\) 到 \(x^k\) 即可。时间复杂度 \(O(nk^2+nk\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define N 100010
#define M 11
#define P 1000000007
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
struct seg{
int l,r;
bool operator<(const seg &t)const{
return l<t.l;
}
}s[N];
int k,C[M][M];
struct node{
int dat[11];
node(){for(int i=0;i<=k;i++)dat[i]=0;}
};
#define dat(x,y) t[x].val.dat[y]
#define tag(x) t[x].tag
struct Tree{
node val;int tag;
}t[N<<3];
#define ls p<<1
#define rs p<<1|1
void build(int p,int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
void pushup(int p){
for(int i=0;i<=k;i++)
dat(p,i)=(dat(ls,i)+dat(rs,i))%P;
}
void pushdown(int p){
if(tag(p)==1)return;
for(int i=0;i<=k;i++){
dat(ls,i)=1ll*dat(ls,i)*tag(p)%P;
dat(rs,i)=1ll*dat(rs,i)*tag(p)%P;
}
tag(ls)=1ll*tag(ls)*tag(p)%P;
tag(rs)=1ll*tag(rs)*tag(p)%P;
tag(p)=1;
}
void add(int p,int l,int r,int x,node v){
if(l==r){
for(int i=0;i<=k;i++)
dat(p,i)=(dat(p,i)+v.dat[i])%P;
return;
}
pushdown(p);
int mid=(l+r)>>1;
if(x<=mid)add(ls,l,mid,x,v);
else add(rs,mid+1,r,x,v);
pushup(p);
}
void mul(int p,int l,int r,int L,int R,int v){
if(L>R)return;
if(L<=l&&r<=R){
tag(p)=1ll*tag(p)*v%P;
for(int i=0;i<=k;i++)
dat(p,i)=1ll*dat(p,i)*v%P;
return;
}
int mid=(l+r)>>1;
pushdown(p);
if(L<=mid)mul(ls,l,mid,L,R,v);
if(R>mid)mul(rs,mid+1,r,L,R,v);
pushup(p);
}
node query(int p,int l,int r,int L,int R){
if(L>R){return node();}
if(L<=l&&r<=R)return t[p].val;
int mid=(l+r)>>1;node ret;
pushdown(p);
if(L<=mid)ret=query(ls,l,mid,L,R);
if(R>mid){
node tp=query(rs,mid+1,r,L,R);
for(int i=0;i<=k;i++)
ret.dat[i]=(ret.dat[i]+tp.dat[i])%P;
}
return ret;
}
int n,lim;
int main(){
n=read(),lim=n*2,k=read();
C[0][0]=1;
for(int i=1;i<=k;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++)
C[i][j]=C[i-1][j-1]+C[i-1][j];
}
build(1,0,lim);
node ori;
ori.dat[0]=1,add(1,0,lim,0,ori);
for(int i=1;i<=n;i++)
s[i].l=read(),s[i].r=read();
sort(s+1,s+1+n);
node cur,tp;
for(int o=1,l,r;o<=n;o++){
l=s[o].l,r=s[o].r;
tp=query(1,0,lim,0,l-1);
for(int i=0;i<=k;i++){
cur.dat[i]=0;
for(int j=0;j<=i;j++)
cur.dat[i]=(cur.dat[i]+1ll*C[i][j]*tp.dat[j])%P;
}
tp=query(1,0,lim,l,r-1);
for(int i=0;i<=k;i++)
cur.dat[i]=(cur.dat[i]+tp.dat[i])%P;
add(1,1,lim,r,cur);
mul(1,1,lim,r+1,lim,2);
}
printf("%d\n",dat(1,k));
return 0;
}