===A=== ===B=== **树剖+线段树+网络流(最大权闭合子图)** 给定一棵n个节点的树,有边权(维护成本),给定m组顶点,代表一条线路,同时给出了该线路带来的利润。可以删除树上的边,以及放弃若干条线路,求:最大利润。 问题即求最大权闭合子图。 建图方式:建立汇点和源点,将源点与m条线路相连,边权为利润,将汇点与树上节点相连,边权为成本,若第i条线路需要经过树上的第j条边,就把线路i和树边j连接,边权为INF。 最后的答案为:全部利润和-该图最大流 由于点的数量为1e4,最坏情况有1e8条边,无法直接建图,可以用线段树优化建图。对给出的树进行树链剖分,将每条线路的路径转化为若干连续区间,再使用线段树优化对区间的连边(若区间[L,R]在线段树上用一个节点维护,则直接将边连到改节点,不需要连到叶节点)。优化过的图,点数为5n+m=6e4,边数为5n+m(1+logn)=2e5。 **AC代码** #include using namespace std; const int maxn=1e6+5,INF=2e9; int n,m,u,v,c,x,y; vector >ve[maxn]; int nxt[maxn],to[maxn],head[maxn],val[maxn],cur[maxn]; int cnt; void add_edge(int u,int v,int va){ to[cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; val[cnt]=va; cnt++; } int sz[maxn],hson[maxn],fa[maxn],dep[maxn],w[maxn]; int id[maxn],re[maxn],idx,top[maxn]; void dfs1(int p,int Fa,int weigh){ sz[p]=1; fa[p]=Fa; dep[p]=dep[Fa]+1; w[p]=weigh; int SIZE=ve[p].size(); int maxx=0; for(int i=0;i>1; build(l,mid,rt<<1); build(mid+1,r,rt<<1|1); pushup(rt); } void query(int l,int r,int L,int R,int va,int rt){ if(l>=L&&r<=R){ add_edge(va,rt,INF); add_edge(rt,va,0); return; } int mid=(l+r)>>1; if(L<=mid)query(l,mid,L,R,va,rt<<1); if(R>mid)query(mid+1,r,L,R,va,rt<<1|1); } int dis[maxn]; bool f[maxn]; queuequ; bool bfs(){ memset(dis,0,sizeof(dis)); memset(f,0,sizeof(f)); dis[0]=1; f[0]=1; qu.push(0); while(!qu.empty()){ int fr=qu.front(); qu.pop(); for(int i=head[fr];i!=-1;i=nxt[i]){ if(val[i]>0&&dis[to[i]]==0){ dis[to[i]]=dis[fr]+1; if(!f[to[i]]){ f[to[i]]=1; qu.push(to[i]); } } } } return dis[en]>0; } int dfs(int p,int maxflow){ if(p==en)return maxflow; int rest=maxflow; for(int i=cur[p];i!=-1&&rest>0;i=nxt[i]){ cur[p]=i; if(val[i]>0&&dis[to[i]]==dis[p]+1){ int tmp=dfs(to[i],min(rest,val[i])); val[i]-=tmp; val[i^1]+=tmp; rest-=tmp; } } return maxflow-rest; } int dinic(){ int res=0; while(bfs()){ int tmp=0; for(int i=0;i<=4*n+m+1;i++)cur[i]=head[i]; do{ tmp=dfs(0,INF); res+=tmp; }while(tmp>0); } return res; } int main(){ cin>>n>>m; memset(head,-1,sizeof(head)); for(int i=1;i>u>>v>>c; ve[v].push_back(make_pair(u,c)); ve[u].push_back(make_pair(v,c)); } dep[1]=1; dfs1(1,0,0); dfs2(1,0,1); en=4*n+m+1; build(1,n,1); int ans=0; for(int i=1;i<=m;i++){ cin>>u>>v>>x>>y; if(x<=y)continue; add_edge(0,4*n+i,x-y); add_edge(4*n+i,0,0); ans+=x-y; while(top[u]!=top[v]){ if(dep[top[u]] ===C=== ===D=== 贪心 n个人共m道菜,循环选取k道,每个人对不同菜的喜爱程度给出,每个人都希望这k道菜的喜爱程度的和最大 感性地,预知前人的选择是没有意义的,没人能对干预过去,轮到他们的时候前人的结果就已经确定了 只会考虑后人的选择,那么,最后一个人一定有能力选择自己最喜欢的菜,以此类推 每个人只需要点后人点过的菜以外自己最喜欢的菜,就可以进行回推了 **AC代码** #include using namespace std; typedef pair PII; const int N = 2010; int a[N][N]; PII b[N][N]; int vis[N]; int main(){ int t; cin>>t; while(t--){ int n,m,k; cin>>n>>m>>k; for(int i=0;i=0;--i){ sort(b[i],b[i]+m); for(int j=m-1;j>=0;--j){ if(!vis[b[i][j].second]){ vis[b[i][j].second] = 1; break; } } } for(int i=0;i ===E=== 这个数量级的k,基本一眼枚举,那么问题其实就变成了对于固定的x和10的幂次,如何快速找到满足条件的y 由于这个函数单调,只需要找到第一个使得该函数大于等于乘积的y即可,我的第一感觉是二分,直接让队长写了 赛后一想这个函数太简单了可以直接开方,然后稍微调整一下即可剩省一个log的复杂度,对于复杂的单调函数可以考虑二分 **AC代码** #include typedef long long ll; using namespace std; int main(){ int t; cin>>t; while(t--){ int flag = 0; ll ans = 0; ll tmp = 1; ll x; cin>>x; for(int k=0;k<18;++k){ ans = (ll)sqrt(x*tmp); if(ans>=(ll(1e9))) break; while(ans*ans ===F=== ===G=== ===H=== ===I=== 签到题 给定n*m大小的棋盘,按五子棋规则构造两人和棋情况 结论很显然 如果行数为偶数,对每一行构造一个连续四个断一个即可,下一行和上一行的情况完全相反 如果行数为奇数,考虑前n-1行采取偶数行情况的构造,最后一行两个棋子交替即可 本来第一直觉以为是考察五子棋的日字八卦阵,结果发现我属实想多了(虽然正解是这么做的,但我觉得很没必要) 八卦阵是和棋局面的充分条件,并非必要条件 **AC代码** #include using namespace std; int main(){ int t; cin>>t; while(t--){ int n,m; cin>>n>>m; int flag = 1; for(int i=0;i<(n%2?n-1:n);++i){ int count = 1; for(int j=0;j0){ if(count<=4){ printf("x"); count++; } else{ count = 1; printf("o"); } } else{ if(count<=4){ printf("o"); count++; } else{ count = 1; printf("x"); } } } flag = -flag; printf("\n"); } if(n%2){ for(int j=0;j ===J=== ===K=== ===L===