2019-2020 ICPC Asia Jakarta Regional Contest
官方題解(需要翻牆)
A.Copying Homework
簽到,
B. Cleaning Robots
表示在以爲根節點的子樹中,節點狀態爲的方案數。
,表示是一條路徑的端點,且和的其它兒子節點所在路徑沒有產生衝突(即融合成一條路徑,那麼其它兒子節點一定不處於路徑的端點處,否則所在的路徑會和其兒子節點路徑融合成一條路徑)。
,表示是一條路徑的端點,且和的其它兒子節點所在路徑會產生衝突(即融合成一條路徑,那麼一定存在某個兒子節點處於路徑的端點處),那麼此時節點必須向父節點延伸。
,表示處於一條路徑的中間位置。
則狀態轉移如下:
然後我們枚舉的兒子節點(以下、均爲的兒子)
其中需要枚舉任意2個兒子兩兩結合成一條路徑,轉移可以通過前綴和來優化,因爲可能存在爲爲的情況,建議轉移時不要用除法。
#include<bits/stdc++.h>
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;
}
C.Even Path
可以發現,經過路徑上的所有和的奇偶性必須相同,前綴和判斷一下就行。
D.Find String in a Grid
官方題解:
E .Songwriter
可以求出位於位置的最長下降長度,即位置可能的最小值,然後從前往後推導,根據大小關係以及前後差值的大小來決定是否需要補差值。
然後最後再推導一遍,把補差值的影響消除掉。
#include<bits/stdc++.h>
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;
}
F .Regular Forestation
首先"good cutting point"一定是位於這個樹的重心,由於樹的重心最多2個,我們枚舉重心作爲good cutting point,然後求出分割開的各個子樹的最小表示法,然後進行比較,如果各個子樹的最小表示法相同,即該重心爲good cutting point。
求子樹的最小表示法時,我們需要確定這個子樹的根,可以選擇子樹的重心作爲根,這樣這個有根樹的最小表示法可以唯一確定該形態的樹。
#include<bits/stdc++.h>
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;
}
G.Performance Review
首先不考慮修改的影響,我們求出Randall在每一年的名次。
考慮修改的情況,每一次修改,名次要麼加1要麼減1,那麼我們就把這個加1減1的影響加到後面年份的名次裏即可,只要存在一個年份Randall的名次大於n了,就說明她被淘汰了,用線段樹維護即可。
#include<bits/stdc++.h>
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;
}
H.Twin Buildings
假設所有的,我們按從小到大排序,然後枚舉作爲一條邊,則。其中用個後綴即可記錄。注意數據值較大,用double會WA。
#include<bits/stdc++.h>
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;
}
I.Mission Possible
官方題解:
J.Tiling Terrace
考慮到障礙最多隻有50個,用表示前個位置中,放置了個type3的地磚的情況下,所能放置的最多type2地磚個數,這樣單個未被利用的位置就會達到最少,然後我們根據價值和數量情況,用type1地磚去替換type2地磚(或者不替換)。
#include<bits/stdc++.h>
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;
}
K.Addition Robot
可以發現function f(L, R, A, B):實際上可以轉化爲矩陣乘法的形式,可以把每一個字符轉化爲相應的矩陣。
我們建立線段樹,節點存儲矩陣信息即可。
#include<bits/stdc++.h>
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;
}
L .Road Construction
建立一個二分圖,二分圖的其中一邊節點代表城市之間的邊,另一邊節點代表工人,不過這樣的二分圖的邊數達到。
我們可以把節點工人替換成材料,每個材料有對應的工人數量,這樣總邊數爲。
然後就是匹配過程,因爲有條邊,所以這個城市構成了一個圖,這個圖有且僅有一個環,那麼位於環外的邊都需要建成,環內至少要建(環的大小-1)條邊。那麼匹配的時候先對環外的邊進行匹配,然後再對環內的邊進行匹配,複雜度。
#include<bits/stdc++.h>
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;
}