B-A Funny Bipartite Graph
题意:给出每一边的点数都不超过
n
≤
18
n\leq18
n≤18的二分图,对左边的点标号
i
i
i从
1
1
1到
n
n
n,对右边的点标号
j
j
j从
1
1
1到
n
n
n,数据保证不会出现
i
i
i到
j
j
j的连边
(
i
>
j
)
(i>j)
(i>j),保证左边的每个点的度数都在
1
1
1到
3
3
3之间。并且给出一个矩阵
A
A
A,在这个矩阵中,如果
A
i
,
j
=
1
A_{i,j}=1
Ai,j=1,那么左边的点
i
i
i和右边的点
j
j
j不可以同时出现,并且要求右边所有的点都有连边。求所有方案中
∑
i
=
1
n
w
i
d
e
g
i
\sum_{i=1}^{n}w_{i}^{deg_{i}}
∑i=1nwidegi的最小值。
做法:特判掉右边存在某个点无边可连的情形,直接输出
−
1
-1
−1。否则由于
w
i
w_{i}
wi为正,对于右边的点没有必要连超过
1
1
1条边,因此搜索的上界不超过
2
∗
18
∗
3
16
2*18*3^{16}
2∗18∗316,且有不少的剪枝空间,可以通过。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
char str[20];
int g[20][20],A[20][20];
int w[20],tag[20],deg[20];
vector<int> go[20],con[20];
int ans=2e9;
int cal(int x,int y) {
if(!y) return 0;
int ans=1;
for(int i=1;i<=y;++i)
ans=(ans*x);
return ans;
}
int n;
void dfs(int cur,int A) {
if(cur==n+1) { ans=min(ans,A);return; }
if(A>=ans) return;
for(auto &v:go[cur]) {
if(!tag[v]) {
for(auto &x:con[v]) tag[x]++;
deg[v]++;
dfs(cur+1,A+cal(w[v],deg[v])-cal(w[v],deg[v]-1));
deg[v]--;
for(auto &x:con[v]) tag[x]--;
}
}
}
void init() {
memset(tag,0,sizeof(tag));
memset(deg,0,sizeof(deg));
for(int i=1;i<=n;i++) {
go[i].clear();
con[i].clear();
}
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
init();
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%s",str+1);
for(int j=1;j<=n;j++) {
g[i][j]=str[j]-'0';
if(g[i][j]) go[j].push_back(i);
}
}
for(int i=1;i<=n;i++) {
scanf("%s",str+1);
for(int j=1;j<=n;j++) {
A[i][j]=str[j]-'0';
if(A[i][j]) {
con[i].push_back(j);
con[j].push_back(i);
}
}
}
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
bool ok=1;
for(int i=1;i<=n;i++) {
if(!go[i].size()) {
ok=0;
break;
}
}
if(!ok) puts("-1");
else {
ans=2e9;
dfs(1,0);
if(ans==2e9) puts("-1");
else printf("%d\n",ans);
}
}
return 0;
}
C-And and Pair
题意:给出一个长度不超过
1
e
5
1e5
1e5的二进制01串,然后求问有多少个点对
(
i
,
j
)
(i,j)
(i,j)满足
0
≤
j
≤
i
≤
n
0 \leq j \leq i \leq n
0≤j≤i≤n并且
i
&
n
=
i
i\&n=i
i&n=i并且
i
&
j
=
0
i\&j=0
i&j=0.
做法:显然
(
0
,
0
)
(0,0)
(0,0)是一个答案,否则就可以枚举
i
i
i的二进制的最高位的位置,统计除去最高位的低位
1
1
1的个数
x
x
x以及
0
0
0的个数
y
y
y。
对于
0
0
0的位置,
i
i
i当前位一定为
0
0
0,
j
j
j当前一定有两种选择,所以为
2
y
2^{y}
2y
对于
1
1
1的位置,枚举
i
i
i填
1
1
1的位置个数,如果
i
i
i填
1
1
1显然
j
j
j只能填
0
0
0,而
i
i
i填
0
0
0则
j
j
j有两种选择,所以为
∑
i
=
0
x
C
x
i
2
x
−
i
=
3
x
\sum_{i=0}^{x}C_{x}^{i}2^{x-i}=3^{x}
∑i=0xCxi2x−i=3x。
每个最高位对答案的贡献是
3
x
∗
2
y
3^{x}*2^{y}
3x∗2y。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int mod=1e9+7;
const int N=1e5+7;
char s[N];
int fpow(ll x,int y) {
ll ans=1;
while(y) {
if(y&1) ans=(ans*x)%mod;
x=(x*x)%mod;
y>>=1;
}
return ans;
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
ll ans=0;
scanf("%s",s+1);
int n=strlen(s+1);
int _1=0,_0=0;
for(int i=n;i>=1;i--) {
if(s[i]=='1') {
ans+=1ll*fpow(3,_1)*fpow(2,_0)%mod;
ans%=mod;
}
if(s[i]=='1') _1++;
else _0++;
}
printf("%lld\n",(ans+1)%mod);
}
return 0;
}
E-Bob’s Problem
题意:给出一张
V
≤
5
e
4
V\leq5e4
V≤5e4,
E
≤
5
e
5
E\leq5e5
E≤5e5的带权图,边分为黑边和白边,然后要求你选出一个边集,边集里面的白边的条数不超过
k
k
k,使得这个图是完全连通的,如果无法连通输出
−
1
-1
−1,否则求出最大的权值和。
做法:按照先加黑边再从权值从大往小加白边的顺序向图中加边。维护一个最大生成树,黑边的权值全部加入到答案,白边最多加
k
k
k条。然后判断是否已经连通,否则在判断白边是否加满
k
k
k条,若为加满则把剩余白边从大到小补满到图中。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=1e6+5;
struct Edge{
int u,v,w,c;
bool operator <(const Edge &rhs) const {
if(c<rhs.c) return 1;
else if(c==rhs.c&&w>rhs.w) return 1;
else return 0;
}
}e[M];
int fa[M];
int fnd(int x) {
if(fa[x]==x) return x;
else return fa[x]=fnd(fa[x]);
}
bool cmp(int a,int b) {
return a>b;
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++) {
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].c);
}
sort(e+1,e+1+m);
ll ans=0;
int cnt=0;
vector<int> rest;
rest.clear();
for(int i=1;i<=m;i++) {
if(e[i].c==0) {
int u=e[i].u;
int v=e[i].v;
int U=fnd(u);
int V=fnd(v);
if(U==V) ans+=e[i].w;
else fa[U]=V,ans+=e[i].w;
}
else if(e[i].c==1) {
int u=e[i].u;
int v=e[i].v;
int U=fnd(u);
int V=fnd(v);
if(U==V) rest.push_back(e[i].w);
else {
fa[U]=V;
ans+=e[i].w;
cnt++;
if(cnt==k) break;
}
}
}
set<int> s;
s.clear();
for(int i=1;i<=n;i++)
s.insert(fnd(i));
if(s.size()==1) {
sort(rest.begin(),rest.end(),cmp);
for(int i=0;i<min(k-cnt,(int)rest.size());i++) ans+=rest[i];
printf("%lld\n",ans);
}
else {
printf("%d\n",-1);
}
}
return 0;
}
G-Eating Plan
题意:给出一个
n
≤
1
e
5
n\leq1e5
n≤1e5的序列,序列的值是全排列的阶乘模上998857459,然后给出若干给查询,每次查询给出一个数
t
t
t,求最小的区间长度
x
x
x,使得某个区间
[
L
,
R
]
[L,R]
[L,R]满足
R
−
L
+
1
=
x
R-L+1=x
R−L+1=x并且
∑
i
=
1
n
a
i
≥
x
\sum_{i=1}^{n}a_{i}\geq x
∑i=1nai≥x。
做法:注意到998857459是2802的倍数,因此序列中实际有效的值只有2802个,通过2802*2802的预处理对所有的区间暴力按照区间和排序,同时维护区间长度后缀最小值,这样就可以做到二分做到单次
l
o
g
log
log的查询。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=998857459;
struct Query {
int val,id;
}a[3000];
int tot=0;
int fac[3000];
struct Two {
int val,len;
bool operator <(const Two &rhs) const {
return val<rhs.val;
}
}b[2804*2804/2];
int mi[2804*2804/2];
int p=0;
int main() {
fac[0]=1;
for(int i=1;i<=2802;i++)
fac[i]=(1ll*fac[i-1]*i)%mod;
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
int x;
scanf("%d",&x);
if(x<=2802) {
a[++tot].val=fac[x];
a[tot].id=i;
}
}
for(int i=1;i<=tot;i++) {
int sum=0;
for(int j=i;j<=tot;j++) {
sum=(sum+a[j].val)%mod;
int len=a[j].id-a[i].id+1;
b[++p].val=sum;
b[p].len=len;
}
}
sort(b+1,b+1+p);
for(int i=p;i>=1;i--) {
if(i==p) mi[i]=b[i].len;
else mi[i]=min(b[i].len,mi[i+1]);
}
while(m--) {
int x;
scanf("%d",&x);
int id=lower_bound(b+1,b+1+p,Two{x,0})-b;
if(id==p+1) puts("-1");
else printf("%d\n",mi[id]);
}
return 0;
}
K-Tree
题意:给出一个
n
≤
1
e
5
n\leq1e5
n≤1e5的树,求问这个树上有多少个点对
(
x
,
y
)
(x,y)
(x,y)满足:
1.
1.
1. 两个点
x
,
y
x,y
x,y互相不会成为彼此的祖先。
2.
2.
2. 两个点的权值
w
x
+
w
y
=
2
∗
w
l
c
a
(
x
,
y
)
w_{x}+w_{y}=2*w_{lca(x,y)}
wx+wy=2∗wlca(x,y),
l
c
a
(
x
,
y
)
lca(x,y)
lca(x,y)是
x
x
x和
y
y
y的最近公共祖先。
3.
3.
3. 两个点的距离不会超过
k
k
k。
做法:考虑启发式合并,先对树重链剖分,然后对于每个子树
i
i
i的答案,把
i
i
i作为
l
c
a
lca
lca统计答案,先计算轻边但不保留信息,再计算重边且保留信息,最后再去计算轻边统计答案。用平衡树维护每个权值对于所有点的深度,暴力统计。
#include<bits/stdc++.h>
#include<bits/extc++.h>
typedef long long ll;
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> pii;
const int N=2e5+7;
int n,k;
ll ans=0;
vector<int> go[N];
int w[N];
int sz[N],d[N],son[N];
tree<pii, null_type, less<pii>, rb_tree_tag, tree_order_statistics_node_update> t[N];
void dfs(int u) {
sz[u]=1;
for(auto &v:go[u]) {
d[v]=d[u]+1;
dfs(v);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]])
son[u]=v;
}
}
void merge(int u,int lca) {
int x=2*w[lca]-w[u];
if(x>=0) ans+=t[x].order_of_key(pii(2*d[lca]-d[u]+k,2e9));
for(auto &v:go[u]) merge(v,lca);
}
void update(int u,int lca,int opt) {
if(opt==1) t[w[u]].insert(pii(d[u],u));
else if(opt==-1) t[w[u]].erase(pii(d[u],u));
for(auto &v:go[u]) update(v,lca,opt);
}
void dsu(int u,bool ok) {
for(auto &v:go[u]) {
if(v==son[u]) continue;
dsu(v,0);
}
if(son[u]) dsu(son[u],1);
for(auto &v:go[u]) {
if(v==son[u]) continue;
merge(v,u);
update(v,u,1);
}
t[w[u]].insert(pii(d[u],u));
if(!ok) update(u,u,-1);
}
int main() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
for(int i=2;i<=n;i++) {
int fa;
scanf("%d",&fa);
go[fa].push_back(i);
}
dfs(1);
dsu(1,1);
printf("%lld\n",2*ans);
return 0;
}
L-Who is the Champion
题意:签到题,给出足球赛事的规则。
做法:按照题意模拟。
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int mp[N][N];
struct Player {
int grade,score,id;
bool operator <(const Player &rhs) const {
if(grade>rhs.grade) return 1;
else if(grade==rhs.grade&&score>rhs.score) return 1;
else return 0;
}
}a[N];
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
scanf("%d",&mp[i][j]);
}
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(j==i) continue;
if(mp[i][j]>mp[j][i]) a[i].grade+=3;
else if(mp[i][j]==mp[j][i]) a[i].grade+=1;
a[i].score+=mp[i][j]-mp[j][i];
}
a[i].id=i;
}
sort(a+1,a+1+n);
if(n==1) printf("%d\n",a[1].id);
else {
if(a[1].grade==a[2].grade&&a[1].score==a[2].score)
puts("play-offs");
else printf("%d\n",a[1].id);
}
return 0;
}