#题解
讲道理,今天的题还是一如既往地没人性。虽然还是内部资料没有办法公布题面,我还是尽量概括吧…今天比昨天好一点…120分orz。
###第一题——城市轰炸(bomb)
【题目描述】
- 给定 n n n个点 m m m条有向边,任意一条边的出点被轰炸时入点不能够被轰炸。求最少需要多少次才可以将这张图完全轰炸。
- 讲道理,真的不喜欢大清早做这个图论题…早上没什么思路,打了把dfs草草结束,一份没拿。
- 标准答案是利用tarjan求出强联通分量然后进行缩点。但是由于只给了512M(其实很大了orz),所以还是会爆栈。需要手开展打tarjan(没错是两个栈)。然后就变成了tarjan的模板题orz。
- 为什么要缩点?因为任意一个点被轰炸之后,他边的入点就不能被轰炸。而一个强联通分量当中可以完全看做从任意一个点进入,另一个点出来的一条链。链上相隔就可以进行轰炸了。而且由于最长的路径的轰炸次数一定大于其他路径的轰炸次数,其他路径上的轰炸次数会被覆盖。
- 那么在这个点上进行缩点,将点的个数赋给缩完点之后的点权。然后求最长权值的路径就是所轰炸的最长路径的次数。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
void fff(){
freopen("bomb.in","r",stdin);
freopen("bomb.out","w",stdout);
}
const int MAXN=1000100;
int nxt[MAXN],first[MAXN],en[MAXN],tot=0;
void add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
en[tot]=y;
}
int d[MAXN],f[MAXN],q[MAXN];
int nxt1[MAXN],first1[MAXN],en1[MAXN];
void add1(int x,int y){
nxt1[++tot]=first1[x];
first1[x]=tot;
en1[tot]=y;
d[y]++;
}
int n,m;
bool vis[MAXN],bz[MAXN];
int stack[MAXN],MS[MAXN],dfn[MAXN];
int g[MAXN],low[MAXN],r[MAXN],bel[MAXN];
int top,num;
void tarjan(int x){
MS[MS[0]=1]=x;
while (MS[0]){
if(!dfn[x=MS[MS[0]]]){
dfn[x]=low[x]=++tot;
r[stack[++top]=x]=first[x];
bz[x]=vis[x]=true;
}
bool enter=false;
for (int i=r[x];i;i=nxt[i]){
if(!vis[en[i]]){
r[x]=nxt[i];
MS[++MS[0]]=en[i];
enter=true;
break;
}else{
if(bz[en[i]]){
low[x]=min(low[x],dfn[en[i]]);
}
}
}
if(enter) continue;
if(dfn[x]==low[x]){
num++;
do{
bel[stack[top]]=num;
bz[stack[top--]]=false;
}while(stack[top+1]!=x);
}
low[MS[--MS[0]]]=min(low[MS[MS[0]]],low[x]);
}
}
int ans=0;
int main(){
fff();
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
}
tot=0;
for (int i=1;i<=n;i++){
if(!vis[i]) tarjan(i);
}
tot=0;
for(int i=1;i<=n;i++){
g[bel[i]]++;
for (int j=first[i];j;j=nxt[j])
if(bel[i]^bel[en[j]]) add1(bel[i],bel[en[j]]);
}
int l=0,r=0;
for (int i=1;i<=n;i++){
if(!d[i])f[q[++r]=i]=g[i];
}
while (l<r){
int x=q[++l];
if(!first1[x]) ans=max(ans,f[x]);
for (int i=first1[x];i;i=nxt1[i]){
f[en1[i]]=max(f[en1[i]],f[x]+g[en1[i]]);
if(!--d[en1[i]]) q[++r]=en1[i];
}
}
printf("%d\n",ans);
return 0;
}
###第二题——密室(room)
【题目描述】
- 给出一幅图,有n个点和m条有向边,对于每一条边都有经过的条件称为key。图上存在k( k ≤ 10 k\leq10 k≤10)种key,每个点有若干种,每条路径需要有若干种,key可以叠加,使用后不会消失。求从点1出发后到点n需要的最短路径是多少(边权为1)
- 一看有叠加,k又这么小就知道要用二进制压位。然后类似于dp的做法吧…图上dp…orz
- 这道题我刚开始打错了…打了把spfa还没有记录状态…但拿了95分…我也不知道为什么数据这么水…
- 在想正解的时候考虑图上dp如何优化搜索次数…爆搜估计会爆…但…真的没有爆orz。
- OK其实就一把粗暴的bfs(类似于spfa,更新最短路),存入队列记录一下压缩的钥匙个数。就做好了…这个比第一题简单不要太多…
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define INF 0x3f3f3f
using namespace std;
void fff(){
freopen("room.in","r",stdin);
freopen("room.out","w",stdout);
}
struct Edge{
int x,y;
int key;
};
struct node{
int x,key;
};
const int MAXN=5005;
vector <Edge> edge;
vector <int> G[MAXN];
int n,m,k;
int key_map[MAXN];
int dist[MAXN][5000];
bool visited[MAXN];
queue <node> q;
void bfs(){
int ans=INF;
q.push((node){1,key_map[1]});
memset(dist,INF,sizeof(dist));
dist[1][key_map[1]]=0;
while (!q.empty()){
node &e=q.front();q.pop();
int siz=G[e.x].size();
for (int i=0;i<siz;i++){
Edge e1=edge[G[e.x][i]];
if((e.key&e1.key)==e1.key&&dist[e.x][e.key]+1<dist[e1.y][e.key|key_map[e1.y]]){
dist[e1.y][e.key|key_map[e1.y]]=dist[e.x][e.key]+1;
if(e1.y==n) ans=min(ans,dist[e1.y][e.key|key_map[e1.y]]);
q.push((node){e1.y,e.key|key_map[e1.y]});
}
}
}
if(ans<INF) printf("%d",ans);
else printf("No Solution");
}
int main(){
fff();
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=n;i++){
for (int j=1;j<=k;j++){
int t;
scanf("%d",&t);
key_map[i]=key_map[i]|(t<<j);
}
}
for (int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
Edge e;
e.x=x,e.y=y;
e.key=0;
for (int j=1;j<=k;j++){
int t;
scanf("%d",&t);
e.key=e.key|(t<<j);
}
edge.push_back(e);
G[x].push_back(edge.size()-1);
}
bfs();
return 0;
}
###第三题——序列(seq)
【题目描述】
- 给出长为n的序列,m个查询,求出查询区间 [ l , r ] [l,r] [l,r]内的最长连续值域。
- 首先知道,在区间当中,值域随着区间的大小增大而增大或保持不变,具有单调性。但查询数量实在是太多,所以需要用莫队优化(不懂百度啊…)查询次数,减少对同一个元素的扫描次数。
- 但是…传统的莫队会爆…大概是排序的时候出现了 [ 1 , 2 ] , [ 1 , 99999 ] , [ 2 , 2 ] , [ 2 , 99999 ] . . . [1,2],[1,99999],[2,2],[2,99999]... [1,2],[1,99999],[2,2],[2,99999]...这种沙雕变态数据吧…那我们换个方向存,求下每一个左端点所属的分块,然后先按照左端点的分块排,在按照右端点的单调递增进行排序…这样子左端点所属块每次最多扫到 n \sqrt n n,最多就 m n m\sqrt n mn,而右端点是单调递增的,所以每块的查询在右端点的查询次数不会超过 n n n。所以平均下大概就可以过了吧…
- 然后怎么实现连续值域的求取呢…利用一个bool数组mark,进行标记,再利用左右端点指针进行连续性传递,剪一剪就出来了…然后和最大值比较下,得出答案。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
void fff(){
freopen("seq.in","r",stdin);
freopen("seq.out","w",stdout);
}
const int MAXN=50100;
int n,m,blo_siz;
int a[MAXN],t[MAXN];
struct node{
int l,r,id;
bool operator <(const node x)const{
if((l-1)/blo_siz==(x.l-1)/blo_siz) return r<x.r;
return (l-1)/blo_siz<(x.l-1)/blo_siz;
}
}q[MAXN];
int now,lcur,rcur,cnt;
bool mark[MAXN];
int ans[MAXN];
int lp[MAXN],rp[MAXN];
int did[MAXN][3];
void insert_(int x,bool flag){
if(lcur<=rcur){
int delta=0,y;
x=a[x];
mark[x]=true,lp[x]=rp[x]=x;
if(flag) cnt++,did[cnt][0]=did[cnt][1]=did[cnt][2]=x;
if(x&&mark[x-1]){
delta+=x-lp[x-1];
lp[x]=lp[x-1];
rp[lp[x-1]]=x;
flag?did[cnt][0]=lp[x-1]:0;
}
if(x+1<=n&&mark[x+1]){
y=lp[x];
delta+=rp[x+1]-x;
rp[y]=rp[x+1];
lp[rp[x+1]]=y,flag?did[cnt][1]=rp[x+1]:0;
}
now=max(now,delta+1);
}
}
void recover(int u){
int x=did[u][2],y=did[u][0],z=did[u][1];
mark[x]=false;
if(y<x) rp[y]=x-1,lp[x-1]=y;
if(x<z) rp[x+1]=z,lp[z]=x+1;
}
void solve(){
int tmp,tail;
for (int i=1;i<=m;i++){
if(i==1||(q[i-1].l-1)/blo_siz!=(q[i].l-1)/blo_siz){
memset(mark,0,sizeof(mark));
tail=min(((q[i].l-1)/blo_siz+1)*blo_siz,n)+1;
now=0,lcur=tail,rcur=0;
}
while (rcur<q[i].r)
insert_(++rcur,0);
cnt=0,tmp=now;
while (lcur>q[i].l)
insert_(--lcur,1);
ans[q[i].id]=now;
now=tmp;
lcur=tail;
while (cnt)
recover(cnt--);//还原左端点
}
}
int main(){
fff();
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for (int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
blo_siz=sqrt(n)+1;
sort(q+1,q+m+1);
solve();
for (int i=1;i<=m;i++){
printf("%d\n",ans[i]);
}
return 0;
}