A - Getting Difference
裴蜀定理
#include<bits/stdc++.h>
using namespace std;
int n,k;
int gcd(int x,int y){
return y==0?x:gcd(y,x%y);
}
int main(){
int x,ans=0,mmax=0;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&x),ans=gcd(ans,x),mmax=max(mmax,x);
printf(k%ans==0 && k<=mmax?"POSSIBLE\n":"IMPOSSIBLE\n");
}
B - Sports Festival
考虑每次删掉最大的即可,一直删完,中间肯定出现了最小值。
#include<bits/stdc++.h>
using namespace std;
const int N=310;
int n,m,a[N][N],tot[N],p[N];
bool tf[N];
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
tot[a[i][1]]++;
p[i]=1;
}
int ans=1e9;
for(int i=1;i<=m;i++){
int mmax=0;
for(int j=1;j<=m;j++) mmax=max(mmax,tot[j]);
ans=min(ans,mmax);
int pos=0;
for(int j=1;j<=m;j++) if(tot[j]==mmax){pos=j;break;}
tf[pos]=true;
for(int j=1;j<=n;j++){
while(p[j]<=m && tf[a[j][p[j]]]){
tot[a[j][p[j]]]--;
p[j]++;
tot[a[j][p[j]]]++;
}
}
}
printf("%d\n",ans);
}
C - Coins
智商题
考虑按照
A
i
−
B
i
A_i-B_i
Ai−Bi给他们排序,如果已经选出了
Z
Z
Z个点贡献
C
i
C_i
Ci。剩下的
X
+
Y
X+Y
X+Y个点中,前
X
X
X个贡献的肯定是
A
i
A_i
Ai,后
Y
Y
Y个肯定选
B
i
B_i
Bi。
那么我们枚举分界点,从前面选
X
X
X个出来,从后面选
Y
Y
Y个出来,直接用堆维护就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct node{
int a,b,c;
bool operator<(const node&q)const{
return a-b>q.a-q.b;
}
}s[N];
priority_queue<int> A,B;
long long tota,totb,tb[N];
int X,Y,Z,n;
void insa(int x){
A.push(-x);tota+=x;
if(A.size()>X) tota+=A.top(),A.pop();
}
void insb(int x){
B.push(-x);totb+=x;
if(B.size()>Y) totb+=B.top(),B.pop();
}
int main(){
scanf("%d %d %d",&X,&Y,&Z);n=X+Y+Z;
long long tot=0;
for(int i=1;i<=n;i++) scanf("%d %d %d",&s[i].a,&s[i].b,&s[i].c),tot+=s[i].c;
sort(s+1,s+1+n);
for(int i=1;i<=X;i++) insa(s[i].a-s[i].c);
for(int i=n;i>=X+1;i--) insb(s[i].b-s[i].c),tb[i]=totb;
long long ans=tot+tota+totb;
for(int i=X+1;i<=n-Y;i++){
insa(s[i].a-s[i].c);
ans=max(ans,tot+tota+tb[i+1]);
}
printf("%lld\n",ans);
}
D - Tree and Hamilton Path
答案的上界很容易分析:
如果一条边两边的点数相同,那么就会被计算
n
−
1
n-1
n−1次。
如果不同,那么就是较小的一边点数
∗
2
*2
∗2。
这个上界在 存在一条边使得两边点数都是
n
2
\frac n 2
2n的时候 会达到。
考虑在重心开始走欧拉回路就可以了。
否则就找一条重心相连的最短边减去就可以了,因为走的是一条欧拉通路。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct edge{
int y,nex,c;
}s[N<<1];
int first[N],len=0,n,sz[N],mmin[N],pos;
bool tf=false;
long long ans=0;
void ins(int x,int y,int c){
s[++len]=(edge){y,first[x],c};first[x]=len;
}
void dfs(int x,int fa){
sz[x]=1;
int mmax=0;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa){
dfs(s[i].y,x);
sz[x]+=sz[s[i].y];
mmax=max(mmax,sz[s[i].y]);
if(sz[s[i].y]*2==n) ans-=s[i].c,tf=true;
ans+=2ll*s[i].c*min(sz[s[i].y],n-sz[s[i].y]);
}
if(max(mmax,n-sz[x])*2<=n) pos=x;
}
int main(){
scanf("%d",&n);
int x,y,c;
memset(mmin,63,sizeof(mmin));
for(int i=1;i<n;i++){
scanf("%d %d %d",&x,&y,&c),ins(x,y,c),ins(y,x,c);
mmin[x]=min(mmin[x],c);
mmin[y]=min(mmin[y],c);
}
dfs(1,0);
if(tf) printf("%lld\n",ans);
else printf("%lld\n",ans-mmin[pos]);
}
E - Sightseeing Plan
考虑在中间这个矩形计算答案。
观察到:
∑
i
=
x
1
x
2
∑
j
=
y
1
y
2
(
i
+
j
i
)
=
(
x
2
+
1
+
y
2
+
1
x
2
+
1
)
−
(
x
2
+
1
+
y
1
x
2
+
1
)
−
(
x
1
+
y
2
+
1
x
1
)
+
(
x
1
+
y
1
x
1
)
\sum_{i=x_1}^{x_2}\sum_{j=y_1}^{y_2}\binom {i+j}{i}=\binom{x_2+1+y_2+1}{x_2+1}-\binom{x_2+1+y_1}{x_2+1}-\binom{x_1+y_2+1}{x_1}+\binom{x_1+y_1}{x_1}
∑i=x1x2∑j=y1y2(ii+j)=(x2+1x2+1+y2+1)−(x2+1x2+1+y1)−(x1x1+y2+1)+(x1x1+y1)
发现一个点到一个矩形的路径条数可以转化为到四个点的路径条数,那么现在就变为四个点经过一个矩形到四个点的路径条数了。
枚举一对点,变成单点经过矩阵到单点的方案数,经过矩阵时还要乘上经过矩阵的点数,考虑在入矩阵和矩阵的两个点差分,所以只需要在矩阵上的每一个点计算出经过该点的路径条数,乘上该点的差分贡献即可。
#include<bits/stdc++.h>
using namespace std;
const int N=2000010,mod=1000000007;
int X[6],Y[6],fac[N],inv[N];
int ksm(int x,int t){
int tot=1;
while(t){
if(t&1) tot=1ll*tot*x%mod;
x=1ll*x*x%mod;
t/=2;
}
return tot;
}
int C(int x,int y){
if(x<0 || y<0) return 0;
return 1ll*fac[x+y]*inv[x]%mod*inv[y]%mod;
}
int calc(int x1,int y1,int x2,int y2){
int ans=0;
for(int i=X[2];i<=X[3];i++){
ans=(ans+mod-1ll*(Y[2]+i)*C(i-x1,Y[2]-y1-1)%mod*C(x2-i,y2-Y[2])%mod)%mod;
ans=(ans+1ll*(Y[3]+i+1)*C(i-x1,Y[3]-y1)%mod*C(x2-i,y2-Y[3]-1))%mod;
}
for(int i=Y[2];i<=Y[3];i++){
ans=(ans+mod-1ll*(X[2]+i)*C(X[2]-x1-1,i-y1)%mod*C(x2-X[2],y2-i)%mod)%mod;
ans=(ans+1ll*(X[3]+i+1)*C(X[3]-x1,i-y1)%mod*C(x2-X[3]-1,y2-i))%mod;
}
return ans;
}
int main(){
fac[0]=1;for(int i=1;i<N;i++) fac[i]=1ll*fac[i-1]*i%mod;
inv[N-1]=ksm(fac[N-1],mod-2);for(int i=N-2;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=0;i<6;i++) scanf("%d",&X[i]);
for(int i=0;i<6;i++) scanf("%d",&Y[i]);
X[0]--;Y[0]--;X[5]++;Y[5]++;
int ans=0;
for(int a=0;a<2;a++)
for(int b=0;b<2;b++)
for(int c=0;c<2;c++)
for(int d=0;d<2;d++){
if((a+b+c+d)&1) ans=(ans+mod-calc(X[1-a],Y[1-b],X[4+c],Y[4+d]))%mod;
else ans=(ans+calc(X[1-a],Y[1-b],X[4+c],Y[4+d]))%mod;
}
printf("%d\n",ans);
}
F - Two Trees
神仙题
一点头绪都没有,做法是生生构造出来的。
建立一个超级源,向两棵树的根
首先一个点的度数若在两棵树中的奇偶性不同,那么无解。
否则给度数为奇数的点跨越两棵树连一条边,跑欧拉回路,偶数度的点标为
0
0
0,奇数度的点若连边正向通过,那么标为
1
1
1,否则标为
−
1
-1
−1。
证明考虑每一个点在的所有环,只有一个会经过父亲与其这条边,并对子树造成
+
1
/
−
1
+1/-1
+1/−1的贡献,否则贡献为
0
0
0。
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
struct edge{
int y,nex;
bool tf;
}s[1000010];
int first[N],len=1,n,las=0,a[N];
bool d[N];
void ins(int x,int y){
s[++len]=(edge){y,first[x]};first[x]=len;
s[++len]=(edge){x,first[y]};first[y]=len;
d[x]^=1;d[y]^=1;
}
void dfs(int x){
int y;
while(first[x]!=0){
if(!s[first[x]].tf){
y=s[first[x]].y;
s[first[x]].tf=true;s[first[x]^1].tf=true;
}
else y=-1;
first[x]=s[first[x]].nex;
if(y!=-1) dfs(y);
}
if(las!=0){
if(abs(x-las)==n){
if(x<las) a[x]=-1;
else a[las]=1;
}
}
las=x;
}
int main(){
scanf("%d",&n);
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(x==-1) ins(2*n+1,i);
else ins(x,i);
}
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(x==-1) ins(2*n+1,n+i);
else ins(n+x,n+i);
}
bool tf=true;
for(int i=1;i<=n;i++) if(d[i]^d[n+i]){tf=false;break;}
else if(d[i]) ins(i,n+i);
if(!tf) printf("IMPOSSIBLE\n");
else{
printf("POSSIBLE\n");
dfs(2*n+1);
for(int i=1;i<=n;i++) printf("%d ",a[i]);printf("\n");
}
}