2019-2020 ICPC Asia Jakarta Regional Contest
官方题解(需要)
签到, b [ i ] = n + 1 − a [ i ] b[i]=n+1-a[i] b[i]=n+1−a[i]
用 d [ i ] [ j ] 用d[i][j] 用d[i][j]表示在以 i i i为根节点的子树中, i i i节点状态为 j j j的方案数。
j = 0 j=0 j=0,表示 i i i是一条路径的端点,且和 i i i的其它儿子节点所在路径没有产生冲突(即融合成一条路径,那么其它儿子节点一定不处于路径的端点处,否则 i i i所在的路径会和其儿子节点路径融合成一条路径)。
j = 1 j=1 j=1,表示 i i i是一条路径的端点,且和 i i i的其它儿子节点所在路径会产生冲突(即融合成一条路径,那么一定存在某个儿子节点处于路径的端点处),那么此时节点 i i i必须向父节点延伸。
j = 2 j=2 j=2,表示 i i i处于一条路径的中间位置。
则状态转移如下:
d [ i ] [ 0 ] + = ∏ d [ s o n ] [ 2 ] d[i][0]+=\displaystyle \prod d[son][2] d[i][0]+=∏d[son][2]
然后我们枚举 i i i的儿子节点 s o n son son(以下 j j j、 k k k均为 i i i的儿子)
d [ i ] [ 0 ] + = ( d [ s o n ] [ 0 ] + d [ s o n ] [ 1 ] ) ∗ ∏ j ! = s o n d [ j ] [ 2 ] d[i][0]+=(d[son][0]+d[son][1])*\displaystyle\prod_{j!=son}d[j][2] d[i][0]+=(d[son][0]+d[son][1])∗j!=son∏d[j][2]
d [ i ] [ 1 ] = ( d [ s o n ] [ 0 ] + d [ s o n ] [ 1 ] ) ∗ ( ∏ j ! = s o n ( d [ j ] [ 2 ] + d [ j ] [ 0 ] ) − ∏ j ! = s o n d [ j ] [ 2 ] ) d[i][1]= (d[son][0]+d[son][1])*\big(\displaystyle\prod_{j!=son}(d[j][2]+d[j][0])-\displaystyle\prod_{j!=son}d[j][2]\big) d[i][1]=(d[son][0]+d[son][1])∗(j!=son∏(d[j][2]+d[j][0])−j!=son∏d[j][2])
d [ i ] [ 2 ] = ( d [ s o n 1 ] [ 0 ] + d [ s o n 1 ] [ 1 ] ) ∗ ( d [ s o n 2 ] [ 0 ] + d [ s o n 2 ] [ 1 ] ) ∗ ∏ k ! = s o n 1 & & k ! = s o n 2 ( d [ k ] [ 2 ] + d [ k ] [ 0 ] ) d[i][2]=(d[son_1][0]+d[son_1][1])*(d[son_2][0]+d[son_2][1])*\displaystyle\prod_{k!=son_1 \&\& k!=son_2}(d[k][2]+d[k][0]) d[i][2]=(d[son1][0]+d[son1][1])∗(d[son2][0]+d[son2][1])∗k!=son1&&k!=son2∏(d[k][2]+d[k][0])
其中 d [ i ] [ 2 ] d[i][2] d[i][2]需要枚举任意2个儿子两两结合成一条路径,转移可以通过前缀和来优化,因为可能存在为 d [ s o n ] [ j ] d[son][j] d[son][j]为 0 0 0的情况,建议转移时不要用除法。
#include
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>e[MAX];
ll d[MAX][4];
ll suf[MAX];
ll p_sum[MAX];//前缀积(d[son][2])
ll s_sum[MAX];//后缀积(d[son][2])
ll p_s[MAX];//前缀积(d[son][0]+d[son][2])
ll s_s[MAX];//后缀积(d[son][0]+d[son][2])
void dfs(int k,int fa)
{
for(int i=0;i<e[k].size();i++)if(e[k][i]==fa)swap(e[k][i],e[k][0]);
for(int i=1;i<e[k].size();i++)dfs(e[k][i],k);
suf[e[k].size()]=0;
p_sum[0]=s_sum[e[k].size()]=1;
p_s[0]=s_s[e[k].size()]=1;
for(int i=1;i<e[k].size();i++)
{
int nex=e[k][i];
p_sum[i]=p_sum[i-1]*d[nex][2]%MOD;
p_s[i]=(d[nex][0]+d[nex][2])%MOD*p_s[i-1]%MOD;
}
for(int i=e[k].size()-1;i>=1;i--)
{
int nex=e[k][i];
suf[i]=(d[nex][0]+d[nex][2])%MOD*suf[i+1]%MOD;
(suf[i]+=(d[nex][0]+d[nex][1])%MOD*s_s[i+1]%MOD)%=MOD;
s_sum[i]=s_sum[i+1]*d[nex][2]%MOD;
s_s[i]=(d[nex][0]+d[nex][2])%MOD*s_s[i+1]%MOD;
}
d[k][0]=1;
for(int i=1;i<e[k].size();i++)d[k][0]=d[k][0]*d[e[k][i]][2]%MOD;
for(int i=1;i<e[k].size();i++)
{
int nex=e[k][i];
d[k][0]+=(d[nex][0]+d[nex][1])%MOD*p_sum[i-1]%MOD*s_sum[i+1]%MOD;
d[k][1]+=(d[nex][0]+d[nex][1])%MOD*((p_s[i-1]*s_s[i+1]%MOD-p_sum[i-1]*s_sum[i+1]%MOD+MOD)%MOD)%MOD;
d[k][2]+=(d[nex][0]+d[nex][1])%MOD*suf[i+1]%MOD*p_s[i-1]%MOD;
d[k][0]%=MOD;
d[k][1]%=MOD;
d[k][2]%=MOD;
}
}
int main()
{
int n;
cin>>n;
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
e[1].push_back(0);
memset(d,0,sizeof d);
dfs(1,0);
cout<<(d[1][0]+d[1][2])%MOD<<endl;
return 0;
}
可以发现,经过路径上的所有 R [ x ] R[x] R[x]和 C [ y ] C[y] C[y]的奇偶性必须相同,前缀和判断一下就行。
可以求出位于位置 i i i的最长下降长度,即位置 i i i可能的最小值,然后从前往后推导,根据大小关系以及前后差值的大小来决定是否需要补差值。
然后最后再推导一遍,把补差值的影响消除掉。
#include
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
ll a[MAX];
ll b[MAX];
ll d[MAX];
int main()
{
int n,L,R,K;
cin>>n>>L>>R>>K;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=n;i>=1;i--)
{
if(i==n||a[i+1]>a[i])d[i]=0;
else if(a[i+1]==a[i])d[i]=d[i+1];
else if(a[i+1]<a[i])d[i]=d[i+1]+1;
}
for(int i=1;i<=n;i++)
{
b[i]=d[i]+L;
if(i==1)continue;
if(a[i-1]==a[i])b[i]=b[i-1];
if(a[i-1]>a[i])
{
if(b[i-1]-b[i]>K)b[i]+=b[i-1]-b[i]-K;
}
if(a[i-1]<a[i])
{
b[i]=max(d[i]+L,b[i-1]+1);
if(b[i]-b[i-1]>K)b[i-1]+=b[i]-b[i-1]-K;
}
}
for(int i=n-1;i>=1;i--)
{
if(abs(b[i]-b[i+1])>K)b[i]+=abs(b[i]-b[i+1])-K;
if(a[i]>a[i+1]&&b[i]<=b[i+1])b[i]=b[i+1]+1;
if(a[i]==a[i+1]&&b[i]!=b[i+1])b[i]=b[i+1];
if(a[i]<a[i+1]&&b[i]>=b[i+1])b[i]=b[i-1]-1;
}
if(*max_element(b+1,b+n+1)>R){cout<<-1<<endl;return 0;}
if(*min_element(b+1,b+n+1)<L){cout<<-1<<endl;return 0;}
for(int i=2;i<=n;i++)if(abs(b[i]-b[i-1])>K){cout<<-1<<endl;return 0;}
for(int i=1;i<=n;i++)printf("%lld%c",b[i],i==n?'\n':' ');
return 0;
}
首先"good cutting point"一定是位于这个树的重心,由于树的重心最多2个,我们枚举重心作为good cutting point,然后求出分割开的各个子树的最小表示法,然后进行比较,如果各个子树的最小表示法相同,即该重心为good cutting point。
求子树的最小表示法时,我们需要确定这个子树的根,可以选择子树的重心作为根,这样这个有根树的最小表示法可以唯一确定该形态的树。
#include
using namespace std;
const int MAX=4e3+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>e[MAX];
int siz[MAX],d[MAX];
int DFS(int k,int fa)
{
siz[k]=1;
d[k]=0;
for(int i=0;i<e[k].size();i++)
{
if(e[k][i]==fa)continue;
DFS(e[k][i],k);
siz[k]+=siz[e[k][i]];
d[k]=max(d[k],siz[e[k][i]]);
}
}
void getroot(int k,int fa,int p,pair<int,int>&root)
{
d[k]=max(p-siz[k],d[k]);
if(root.first==0||d[k]<d[root.first])root.first=k;
else if(root.first!=0&&d[k]==d[root.first])root.second=k;
for(int i=0;i<e[k].size();i++)
{
if(e[k][i]==fa)continue;
getroot(e[k][i],k,p,root);
}
}
string dfs(int k,int fa,int T)
{
if(k==0)return "";
vector<string>Q;
for(int i=0;i<e[k].size();i++)
{
int nex=e[k][i];
if(nex==fa||nex==T)continue;
Q.push_back(dfs(nex,k,T));
}
string ans="0";
sort(Q.begin(),Q.end());
for(int i=0;i<Q.size();i++)ans+=Q[i];
ans+="1";
return ans;
}
int cut(int k)
{
if(e[k].size()<2)return -1;
string A,B;
for(int i=0;i<e[k].size();i++)
{
pair<int,int>root={0,0};
DFS(e[k][i],k);
getroot(e[k][i],k,siz[e[k][i]],root);
if(i==0)
{
A=dfs(root.first,0,k);
B=dfs(root.second,0,k);
}
if(A!=dfs(root.first,0,k)&&A!=dfs(root.second,0,k))A="";
if(B!=dfs(root.first,0,k)&&B!=dfs(root.second,0,k))B="";
if(A==""&&B=="")return -1;
}
return (int)e[k].size();
}
int main()
{
int n;
cin>>n;
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
pair<int,int>root={0,0};
DFS(1,0);
getroot(1,0,n,root);
cout<<max(cut(root.first),cut(root.second))<<endl;
return 0;
}
首先不考虑修改的影响,我们求出Randall在每一年的名次。
考虑修改的情况,每一次修改,名次要么加1要么减1,那么我们就把这个加1减1的影响加到后面年份的名次里即可,只要存在一个年份Randall的名次大于n了,就说明她被淘汰了,用线段树维护即可。
#include
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>e[MAX];
int a[MAX];
int b[MAX];
struct lenka
{
int l,r,mi;
int tag;
}A[MAX<<2];
void build(int k,int l,int r)
{
A[k].l=l,A[k].r=r;
A[k].tag=0;
if(l==r){A[k].mi=b[r];return;}
build(2*k,l,(l+r)/2);
build(2*k+1,(l+r)/2+1,r);
A[k].mi=min(A[2*k].mi,A[2*k+1].mi);
}
void add(int k,int x,int y,int z)
{
if(x>y)return;
if(x==A[k].l&&y==A[k].r)
{
A[k].mi+=z;
A[k].tag+=z;
return;
}
if(A[k].tag)
{
add(2*k,A[2*k].l,A[2*k].r,A[k].tag);
add(2*k+1,A[2*k+1].l,A[2*k+1].r,A[k].tag);
A[k].tag=0;
}
if(y<=A[2*k].r)add(2*k,x,y,z);
else if(x>=A[2*k+1].l)add(2*k+1,x,y,z);
else
{
add(2*k,x,A[2*k].r,z);
add(2*k+1,A[2*k+1].l,y,z);
}
A[k].mi=min(A[2*k].mi,A[2*k+1].mi);
}
int main()
{
int n,m,Q;
cin>>n>>m>>Q;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
b[0]=1;
for(int i=2;i<=n;i++)b[0]+=(a[i]<a[1]);
for(int i=1;i<=m;i++)
{
int r,y=0;
scanf("%d",&r);
while(r--)
{
int x;
scanf("%d",&x);
e[i].push_back(x);
if(x<a[1])y++;
}
b[i]=b[i-1]+y-(int)e[i].size();
}
for(int i=m;i>=1;i--)b[i]=b[i-1]-(int)e[i].size();
build(1,1,m);
while(Q--)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(e[x][y-1]<a[1]&&z>a[1])add(1,x+1,m,-1);
if(e[x][y-1]>a[1]&&z<a[1])add(1,x+1,m,1);
e[x][y-1]=z;
puts(A[1].mi<=0?"0":"1");
}
return 0;
}
假设所有的 L i > W i L_i>W_i Li>Wi,我们按 L L L从小到大排序,然后枚举 L i L_i Li作为一条边,则 a n s = m a x ( L i ∗ m i n ( W i , W j ) ) , j > i ans=max(L_i*min(W_i,W_j)),j>i ans=max(Li∗min(Wi,Wj)),j>i。其中 W j W_j Wj用个后缀即可记录。注意数据值较大,用double会WA。
#include
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
struct lenka{ll l,w;}A[MAX];
int cmp(const lenka&x,const lenka&y){return x.l<y.l;}
ll d[MAX];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&A[i].l,&A[i].w);
if(A[i].l<A[i].w)swap(A[i].l,A[i].w);
}
sort(A+1,A+n+1,cmp);
for(int i=n;i>=1;i--)d[i]=(i==n?A[i].w:max(A[i].w,d[i+1]));
ll ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,A[i].l*A[i].w);
if(i<n)ans=max(ans,A[i].l*min(A[i].w,d[i+1])*2);
}
printf("%lld.%lld\n",ans/2,ans%2*5);
return 0;
}
考虑到障碍最多只有50个,用 d [ i ] [ j ] d[i][j] d[i][j]表示前 i i i个位置中,放置了 j j j个type3的地砖的情况下,所能放置的最多type2地砖个数,这样单个未被利用的位置就会达到最少,然后我们根据价值和数量情况,用type1地砖去替换type2地砖(或者不替换)。
#include
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
char s[MAX];
int d[MAX][51];
int BLOCK=0;
int main()
{
int n,m,G1,G2,G3;
cin>>n>>m>>G1>>G2>>G3;
scanf("%s",s+1);
memset(d,-1,sizeof d);
d[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=50;j++)
{
d[i][j]=d[i-1][j];
if(i>=2&&s[i]=='.'&&s[i-1]=='.'&&d[i-2][j]!=-1)d[i][j]=max(d[i][j],d[i-2][j]+1);
if(i>=3&&j&&s[i]=='.'&&s[i-1]=='#'&&s[i-2]=='.')d[i][j]=max(d[i][j],d[i-3][j-1]);
}
for(int i=1;i<=n;i++)BLOCK+=(s[i]=='#');
int ans=0;
for(int i=0;i<=50;i++)
{
if(d[n][i]==-1)continue;
int one=n-d[n][i]*2-i*3-(BLOCK-i);
if(m<=one)ans=max(ans,m*G1+d[n][i]*G2+i*G3);
else
{
ans=max(ans,one*G1+d[n][i]*G2+i*G3);
if(G1*2<=G2)continue;
int res=m-one;
if(res/2>=d[n][i])ans=max(ans,(d[n][i]*2+one)*G1+i*G3);
else
{
if(res%2&&G1>G2)ans=max(ans,((res/2)*2+1+one)*G1+i*G3+(d[n][i]-res/2-1)*G2);
else ans=max(ans,((res/2)*2+one)*G1+i*G3+(d[n][i]-res/2)*G2);
}
}
}
cout<<ans<<endl;
return 0;
}
可以发现function f(L, R, A, B):实际上可以转化为矩阵乘法的形式,可以把每一个字符转化为相应的矩阵。
我们建立线段树,节点存储矩阵信息即可。
#include
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
struct lenka
{
int a[2][2];
lenka(){}
lenka(int x,int x1,int x2,int x3)
{
a[0][0]=x,a[0][1]=x1;
a[1][0]=x2,a[1][1]=x3;
}
};
lenka multiply(const lenka &a,const lenka&b)
{
lenka c=lenka(0,0,0,0);
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
for(int k=0;k<2;k++)(c.a[i][j]+=1ll*a.a[i][k]*b.a[k][j]%MOD)%=MOD;
return c;
}
char s[MAX];
struct Data
{
int l,r;
int tag;
lenka a,b;
}A[MAX<<2];
void build(int k,int l,int r)
{
A[k].l=l,A[k].r=r;
A[k].tag=0;
if(l==r)
{
if(s[r]=='A')A[k].a=lenka(1,0,1,1),A[k].b=lenka(1,1,0,1);
else A[k].a=lenka(1,1,0,1),A[k].b=lenka(1,0,1,1);
return;
}
build(2*k,l,(l+r)/2);
build(2*k+1,(l+r)/2+1,r);
A[k].a=multiply(A[2*k].a,A[2*k+1].a);
A[k].b=multiply(A[2*k].b,A[2*k+1].b);
}
void change(int k,int x,int y)
{
if(x==A[k].l&&y==A[k].r)
{
A[k].tag^=1;
swap(A[k].a,A[k].b);
return;
}
if(A[k].tag)
{
change(2*k,A[2*k].l,A[2*k].r);
change(2*k+1,A[2*k+1].l,A[2*k+1].r);
A[k].tag=0;
}
if(y<=A[2*k].r)change(2*k,x,y);
else if(x>=A[2*k+1].l)change(2*k+1,x,y);
else
{
change(2*k,x,A[2*k].r);
change(2*k+1,A[2*k+1].l,y);
}
A[k].a=multiply(A[2*k].a,A[2*k+1].a);
A[k].b=multiply(A[2*k].b,A[2*k+1].b);
}
lenka ask(int k,int x,int y)
{
if(x==A[k].l&&y==A[k].r)return A[k].a;
if(A[k].tag)
{
change(2*k,A[2*k].l,A[2*k].r);
change(2*k+1,A[2*k+1].l,A[2*k+1].r);
A[k].tag=0;
}
if(y<=A[2*k].r)return ask(2*k,x,y);
if(x>=A[2*k+1].l)return ask(2*k+1,x,y);
lenka L=ask(2*k,x,A[2*k].r);
lenka R=ask(2*k+1,A[2*k+1].l,y);
return multiply(L,R);
}
int main()
{
int n,m;
cin>>n>>m;
scanf("%s",s+1);
build(1,1,n);
while(m--)
{
int op,L,R;
scanf("%d%d%d",&op,&L,&R);
if(op==1)change(1,L,R);
else
{
ll x,y;
scanf("%lld%lld",&x,&y);
lenka ans=ask(1,L,R);
ll X=x*ans.a[0][0]%MOD+y*ans.a[1][0]%MOD;
ll Y=x*ans.a[0][1]%MOD+y*ans.a[1][1]%MOD;
printf("%lld %lld\n",X%MOD,Y%MOD);
}
}
return 0;
}
建立一个二分图,二分图的其中一边节点代表城市之间的边,另一边节点代表工人,不过这样的二分图的边数达到 n ∗ k n*k n∗k。
我们可以把节点工人替换成材料,每个材料有对应的工人数量,这样总边数为 ∑ m i ≤ 10000 \sum m_i\le10000 ∑mi≤10000。
然后就是匹配过程,因为有 n n n条边,所以这 n n n个城市构成了一个图,这个图有且仅有一个环,那么位于环外的边都需要建成,环内至少要建(环的大小-1)条边。那么匹配的时候先对环外的边进行匹配,然后再对环内的边进行匹配,复杂度 O ( n ∗ ∑ m i ) O(n*\sum m_i) O(n∗∑mi)。
#include
using namespace std;
const int MAX=2e4+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>E[2010];
int edge_link[2010];
vector<int>matr_link[MAX];
int v[MAX];
int cnt[MAX];
int dfs(int k)
{
for(int i=E[k].size()-1;i>=0;i--)
{
int nex=E[k][i];
if(v[nex])continue;
v[nex]=1;
if(cnt[nex]>0)
{
cnt[nex]--;
edge_link[k]=nex;
matr_link[nex].push_back(k);
return 1;
}
for(int j=0;j<matr_link[nex].size();j++)
{
if(dfs(matr_link[nex][j]))
{
edge_link[k]=nex;
matr_link[nex][j]=k;
return 1;
}
}
}
return 0;
}
vector<int>edge;
int e[2010][2010];
int in[2010];
void find_cycle(int n)
{
queue<int>p;
for(int i=1;i<=n;i++)if(in[i]==1)p.push(i);
while(!p.empty())
{
int now=p.front();p.pop();
for(int i=1;i<=n;i++)
{
if(e[now][i]==0)continue;
edge.push_back(e[now][i]);
in[i]--;
if(in[i]==1)p.push(i);
}
}
sort(edge.begin(),edge.end());
edge.erase(unique(edge.begin(),edge.end()),edge.end());
}
map<int,int>ma;
struct lenka{int x,y;}EDG[2010];
int worker[2010];
int tot=0;
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
e[x][i]=e[i][x]=i;
EDG[i]=(lenka){i,x};
in[x]++;
in[i]++;
while(y--)
{
scanf("%d",&x);
if(ma[x]==0)ma[x]=++tot;
E[i].push_back(ma[x]);
}
}
for(int i=1;i<=m;i++)
{
scanf("%d",&worker[i]);
if(ma[worker[i]]==0)ma[worker[i]]=++tot;
worker[i]=ma[worker[i]];
}
memset(cnt,0,sizeof cnt);
for(int i=1;i<=m;i++)cnt[worker[i]]++;
find_cycle(n);
int ans=0;
for(int i=0;i<edge.size();i++)
{
//printf("edge %d is outside of the cycle \n",edge[i]);
memset(v,0,sizeof v);
ans+=dfs(edge[i]);
}
if(ans!=(int)edge.size()){puts("-1");return 0;}
for(int i=1;i<=n;i++)
{
if(edge_link[i])continue;
memset(v,0,sizeof v);
ans+=dfs(i);
}
if(ans<n-1){puts("-1");return 0;}
memset(v,0,sizeof v);
for(int i=1;i<=m;i++)
{
int tag=0;
for(int j=1;j<=n;j++)
{
if(v[j]==0&&edge_link[j]==worker[i])
{
tag=v[j]=1;
printf("%d %d\n",EDG[j].x,EDG[j].y);
break;
}
}
if(tag==0)puts("0 0");
}
return 0;
}