一、神经网络
数据:对于50%的数据,n∈[1,100]
对于100%的数据,n∈[1,200]
很简单的一道题,但是只看题目会有点误解。题目中说神经若处于兴奋状态,下一秒向后发消息。我理解成这个要按照时间来,用完后就有成平静态。这样的话U[i]就多减了,而且有问题,只有20分。
实际上是一个点的Ci要把所有他的输入的Cj全部算出来才行。
这样就有两种方法:
1、按照拓扑序,一层一层算过来
2、反向,从输出层往前,用一下记忆化搜索。
我写的是记忆化搜索。这个比较简单,只需反向存一下边就行了。
二、最大字典序排列
数据:对于60%的数据,n∈[1,2000]
对于80%的数据,n∈[1,20000]
对于100%的数据,n∈[1,100000]
不看题就猜这是到dp题,这是惯例。然后猜错了。幸好没仔细想。
正解是贪心,每次从后面选出一个数值最大并且可以交换到这个位子的数。直接实现的话类似于冒泡排序,一位一位交换上来,这样是n^2。但这道题直接这样算不对,这样写有80分。
然后就要写N log N 的了。log N 的不多,这里用的是线段树。
线段树里装的是在[L,R]这个区间里的最大值和这个区间里还有几个数没被用过(即被交换到前面)。不用记录最大值的位置吗?因为是1-N的排列,只要起初记录一下,然后再删除就行了。
Code:
#include<bits/stdc++.h>
using namespace std;
int A[100005],G[100005],Q[100005];//原数组,每个数的位置,哪些数已输出
struct node{int L,R,mx,cnt;}tree[400005];//左右,最大值,有多少个数
void Up(int p){
tree[p].mx=max(tree[p*2].mx,tree[p*2+1].mx);
tree[p].cnt=tree[p*2].cnt+tree[p*2+1].cnt;
}
void Build(int L,int R,int p){
tree[p].L=L;tree[p].R=R;
if(L==R){
tree[p].mx=A[L];
tree[p].cnt=1;
return;
}
int mid=(L+R)/2;
Build(L,mid,p*2);Build(mid+1,R,p*2+1);
Up(p);
}
int Delete(int x,int p){//一边删除,一边计算移动步数
if(tree[p].L==tree[p].R){
tree[p].mx=tree[p].cnt=0;
return 0;
}
int mid=(tree[p].L+tree[p].R)/2;
int del;
if(mid>=x)del=Delete(x,p*2);
else del=Delete(x,p*2+1)+tree[p*2].cnt;
Up(p);
return del;
}
int Query(int K,int p){
if(K<0)return 0;
if(tree[p].cnt<=K)return tree[p].mx;//大区间符合,直接返回
int Lr=Query(K,p*2);//先左边,移动的步数少,
if(tree[p*2].cnt<K)return max(Lr,Query(K-tree[p*2].cnt,p*2+1));//行的话右边,K要减一下是因为右边的也要交换经过左边
return Lr;
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
G[A[i]]=i;Q[i]=1;//记录位置,初始状态
}
Build(1,n,1);
for(int i=1;i<=n;i++){
if(k<=0)break;
int res=Query(k+1,1);//单点查询
printf("%d\n",res);
Q[G[res]]=0;
int del=Delete(G[res],1);
k-=del;//减去res的交换步数
}
for(int i=1;i<=n;i++)
if(Q[i])printf("%d\n",A[i]);//最后没输出的输出
return 0;
}
三、航线规划
数据:对于 30%的数据,n 的范围[2,100],m 的范围[1,500],Q 的范围[1,100]
对于 60%的数据,n 的范围[2,10000],m 的范围[1,30000],Q 的范围[1,20000],数据中没有删边操作
对于 100%的数据,n 的范围[2,30000],m 的范围[1,100000],Q 的范围[1,40000],数据保证任何时候图都是连通,且没有重边和自环
又一道图论神题。兼用前几次考试最后一题的部分思想。
理一下知识点:
1、线段树(可以用BIT代替)
2、路径压缩(并查集实现)
3、LCA(倍增)
4、dfs序(造树)
5、求两点之间的路径上的值
这道题与前面的不一样的地方是,建树时随便建的,反正没用的边迟早会被标记掉,关键路径永远不会被标记掉,而且肯定在所造的树上(否则不用这条边就没办法连通了,反证法,想想很简单的)。然后是题目要删边,干脆先把边删完,最后反着一条一条加回去,这样就一样了。
卡了很久,错了三个地方:
1、LCA手贱把 step=dep[y]-dep[x] 写成 step=dep[x]-dep[y] ,这样step就是负数了,不知道哪里来的60分(全部询问输出0就有60分,这数据……)
2、合并路径时,x和y要先指向自己的父亲节点,否则会重复更新。之前那道题没有加,是因为那个更新不会造成影响,而这个不一样。
3、数组开小了,按照数据一直90,后来把数组扩大5倍对了,然后仔细想发现山区的边可能会重复问,这样就要开两倍,两倍也是对的。(之前开了个1.5倍的,错了)
Code(小长):
#include<bits/stdc++.h>
#define M 100005
using namespace std;
vector<int>edge[M];
set<int>edge1[M];
int n,m,len,cas;
int fa[20][M],fa2[M],ans[M],dep[M],op[M],Q[M];
struct node1{int x,y;}G[M*2],G1[M*2];
int Lt[M],Rt[M],dfsline[M],T;
void f(int x,int fa1){//随机把图变成一棵树
Q[x]=1;
Lt[x]=++T;dfsline[T]=x;
fa[0][x]=fa1;dep[x]=dep[fa1]+1;
for(int i=0;i<(int)edge[x].size();i++){
int y=edge[x][i];
if(y==fa1)continue;
if(edge1[x].find(y)!=edge1[x].end())continue;//要删除的边不能放进去
if(Q[y]){G1[++len]=(node1){x,y};continue;}
f(y,x);
}
Rt[x]=T;
}
struct Tree{//线段树部分
struct node{int L,R,sum;}tree[4*M];
void Build(int L,int R,int p){
tree[p].L=L;tree[p].R=R;tree[p].sum=0;
if(L==R){tree[p].sum=dep[dfsline[L]];return;}
int mid=(L+R)/2;
Build(L,mid,p*2);Build(mid+1,R,p*2+1);
}
int Query(int x,int p){
if(tree[p].L==tree[p].R)return tree[p].sum;
int mid=(tree[p].L+tree[p].R)/2;
if(mid>=x)return tree[p].sum+Query(x,p*2);
else return tree[p].sum+Query(x,p*2+1);
}
void Change(int L,int R,int p){
if(tree[p].L==L&&tree[p].R==R){
tree[p].sum--;return;
}
int mid=(tree[p].L+tree[p].R)/2;
if(mid>=R)Change(L,R,p*2);
else if(mid<L)Change(L,R,p*2+1);
else Change(L,mid,p*2),Change(mid+1,R,p*2+1);
}
}tree;
struct Lca{//LCA部分
void Init(){
for(int j=1;j<20;j++)
for(int i=1;i<=n;i++)
fa[j][i]=fa[j-1][fa[j-1][i]];
}
int LCA(int x,int y){
if(dep[x]>dep[y])swap(x,y);
int step=dep[y]-dep[x];//一开始这里错了
for(int i=0;i<20;i++)
if(step&(1<<i))y=fa[i][y];
if(x==y)return x;
for(int i=19;i>=0;i--)
if(fa[i][x]!=fa[i][y])
x=fa[i][x],y=fa[i][y];
return fa[0][x];
}
}LCA;
int Find(int x){return x==fa2[x]?x:fa2[x]=Find(fa2[x]);}
void Hebing(int x,int y){
x=Find(x),y=Find(y);//这两句一开始没加,然后运行错,要是不加,下面的程序就会执行两次,Tree.Change就会用两次
while(x!=y){
if(dep[x]>=dep[y]){
tree.Change(Lt[x],Rt[x],1);
fa2[x]=fa[0][x];
x=Find(x);
}
else{
tree.Change(Lt[y],Rt[y],1);
fa2[y]=fa[0][y];
y=Find(y);
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&cas);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}
for(int i=1;i<=cas;i++){
scanf("%d%d%d",&op[i],&G[i].x,&G[i].y);
if(op[i]==0)edge1[G[i].x].insert(G[i].y),edge1[G[i].y].insert(G[i].x);//把删除的边存下来
}
f(1,0);//造树
LCA.Init();//LCA预处理
tree.Build(1,n,1);//建线段树
for(int i=1;i<=n;i++)fa2[i]=i;
for(int i=1;i<=len;i++)Hebing(G1[i].x,G1[i].y);
for(int i=cas;i>=1;i--){
if(op[i]==0)Hebing(G[i].x,G[i].y);
else {//这里更以前一样
int lca=LCA.LCA(G[i].x,G[i].y);
int Lres=tree.Query(Lt[G[i].x],1),Rres=tree.Query(Lt[G[i].y],1),LCAres=tree.Query(Lt[lca],1);
ans[i]=Lres+Rres-LCAres*2;
}
}
for(int i=1;i<=cas;i++)
if(op[i])printf("%d\n",ans[i]);
return 0;
}
被第一题坑了,卡了一个多小时,哎……