题意:给出每一边的点数都不超过 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
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;
}
题意:给出一个长度不超过 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
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;
}
题意:给出一张 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
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;
}
题意:给出一个 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
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;
}
题意:给出一个 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
#include
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;
}
题意:签到题,给出足球赛事的规则。
做法:按照题意模拟。
#include
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;
}