1002:http://acm.hdu.edu.cn/showproblem.php?pid=6579
题意:给你n个数,有m次操作,每次操作有两种
1、0 l r 表示查询[l,r]里选任意个数时异或值最大。
2、1 x 表示在n个数后面继续添加1个数x,并令n=n+1
分析:选任意个数使异或值最大肯定是线性基的题,比赛时直接上线段树维护区间线性基,时间复杂度都没算,结果T到飞起....
具体算了一下,至少是O(t*m*logn*30)也就是3e9(居然忘了线性基的合并的时候暴力合并,要*30,以为是1e8),肯定T到飞起,然鹅比赛时有没想到好的办法,只有不断优化想着卡过去,结果。。。肯定悲剧了。
具体分析注意它的修改是在末尾添加一个数,而不是直接修改某个数,所有不一定要有线段树来实现修改,可维护前缀线性基,在末尾添加数并不影响前面的维护的前缀线性基,所以可行。
至于如何取到最大值,那就要贪心一下,每次新加的数尽可能让它作为前缀线性基的基底,因为它更靠右,每次查询最值时,是查询右端点的前缀线性基,看被异或的基底是否在查询区间的左端点右侧,所以该基底越靠右越好,更有可能被选。
Ac code:
///前缀线性基
///可解决不带修改或只在最后面添加数的修改问题
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int a[maxn],f[maxn][25],pos[maxn][25];
void update(int i,int x)
{
int k=i;
for(int j=20; j>=0; j--) f[i][j]=f[i-1][j],pos[i][j]=pos[i-1][j];
for(int j=20; j>=0; j--)
{
if(x&(1<<j))
{
if(!f[i][j])
{
f[i][j]=x;
pos[i][j]=k;
break;
}
else
{
if(k>pos[i][j])///使每个新出现的数尽量靠右
{
swap(k,pos[i][j]);
swap(x,f[i][j]);
}
x^=f[i][j];
}
}
}
}
int main()
{
int n,m;
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
update(i,a[i]);
}
scanf("%d",&m);
int l,r;
while(m--)
{
scanf("%d%d",&l,&r);
int ans=0;
for(int j=20; j>=0; j--)
if((ans^f[r][j])>ans&&pos[r][j]>=l)///查询时利用右端点的前缀线性基,左端点如果小于右端点的前缀线性基的第j位基底,
///说明可以选该基底
ans^=f[r][j];
printf("%d\n",ans);
}
return 0;
}
PS:话说居然是CF一道原题,只是这题强制在线。。
CF原题:https://codeforc.es/contest/1100/problem/F
与上面差不多,连修改都没有了。更简单
Ac code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int a[maxn],f[maxn][25],pos[maxn][25];
void update(int i,int x)
{
int k=i;
for(int j=20; j>=0; j--) f[i][j]=f[i-1][j],pos[i][j]=pos[i-1][j];
for(int j=20; j>=0; j--)
{
if(x&(1<<j))
{
if(!f[i][j])
{
f[i][j]=x;
pos[i][j]=k;
break;
}
else
{
if(k>pos[i][j])
{
swap(k,pos[i][j]);
swap(x,f[i][j]);
}
x^=f[i][j];
}
}
}
}
int main()
{
int n,m;
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
update(i,a[i]);
}
scanf("%d",&m);
int l,r;
while(m--)
{
scanf("%d%d",&l,&r);
int ans=0;
for(int j=20; j>=0; j--)
if((ans^f[r][j])>ans&&pos[r][j]>=l)
ans^=f[r][j];
printf("%d\n",ans);
}
return 0;
}
1005:http://acm.hdu.edu.cn/showproblem.php?pid=6582
题意:给你n个结点,m条边(有可能有重边),要你删除一些边使最短路变大。
分析:就是一个最小割(最大流)的板子题,选出所有最短路径,构成一个新的图,跑一遍最大流就好了。
选出所有最短路上的边的方法:spfa跑出了到1的最短dist后,遍历1->m,如果dist[u]+(u,v)==dist[v]就表明(u,v)这条边一定在最短路径上。
Ac code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+1;
const ll INF=1e18;
int head[maxn],tot;
struct Edge
{
int v,nxt;
ll w;
} edg[maxn];
ll dist[maxn];
bool vis[maxn];
struct edge
{
int from, to;
ll cap, flow;//分别是起点,终点,容量,流量
edge(int u, int v, ll c, ll f):from(u), to(v), cap(c), flow(f) {}
};
int n, m;//n为点数,m为边数
vector<edge>e;//保存所有边的信息
vector<int>G[maxn];//邻接表,G[i][j]保存节点i的第j条边在e数组里面的编号
ll a[maxn];//每个点目前流经的水量
int p[maxn];//p[i]从原点s到终点t的节点i的前一条边的编号
int u[maxn],v[maxn],w[maxn];
void init(int n)
{
tot=0;
memset(head,-1,sizeof head);
for(int i = 0; i <= n; i++)G[i].clear();
e.clear();
}
void addedge(int u, int v, int c)
{
e.push_back(edge(u, v, c, 0));//正向边
e.push_back(edge(v, u, 0, 0));//反向边,容量为0
int m = e.size();
G[u].push_back(m - 2);
G[v].push_back(m - 1);
}
ll Maxflow(int s, int t)//起点为s,终点为t
{
int flow = 0;
for(;;)
{
memset(a, 0, sizeof(a));//从原点s开始放水,最初每个点的水量都为0
queue<int>Q;//BFS拓展队列
Q.push(s);
a[s] = INF;//原点的水设置成INF
while(!Q.empty())
{
int x = Q.front();//取出目前水流到的节点
Q.pop();
for(int i = 0; i < G[x].size(); i++)//所有邻接节点
{
edge& now = e[G[x][i]];
if(!a[now.to] && now.cap > now.flow)
//a[i]为0表示i点还未流到
//now.cap > now.flow 说明这条路还没流满
//同时满足这两个条件,水流可以流过这条路
{
p[now.to] = G[x][i];//反向记录路径
a[now.to] = min(a[x], now.cap - now.flow);
//流到下一点的水量为上一点的水量或者路径上还可以流的最大流量,这两者取最小值
Q.push(now.to);//将下一个节点入队列
}
}
if(a[t])break;//如果已经流到了终点t,退出本次找增广路
}
if(!a[t])break;//如果所有路都已经试过,水不能流到终点,说明已经没有增广路,已经是最大流
for(int u = t; u != s; u = e[p[u]].from)//反向记录路径
{
e[p[u]].flow += a[t];//路径上所有正向边的流量增加流到终点的流量
e[p[u]^1].flow -= a[t];//路径上所有反向边的流量减少流到终点的流量
}
flow += a[t];//最大流加上本次流到终点的流量
}
return flow;
}
void addedge1(int u,int v,ll w)
{
edg[tot].v=v;
edg[tot].w=w;
edg[tot].nxt=head[u];
head[u]=tot++;
}
void spfa(int s)
{
queue<int>q;
for(int i=1; i<=n; i++) dist[i]=INF;
memset(vis,0,sizeof vis);
dist[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; ~i; i=edg[i].nxt)
{
int v=edg[i].v;
if(dist[v]>dist[u]+edg[i].w)
{
dist[v]=dist[u]+edg[i].w;
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
}
void addedge2(int m)
{
for(int i=1; i<=m; i++)
{
if(dist[u[i]]+w[i]==dist[v[i]]) ///求所有最短路径上的边
addedge(u[i],v[i],w[i]);
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init(n);
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
addedge1(u[i],v[i],w[i]);
}
spfa(1);
addedge2(m);
printf("%lld\n",Maxflow(1,n));
}
return 0;
}