小清新思维题
0x01
https://www.luogu.com.cn/problem/P1119
首先离线原理,排序原理,复杂度原理。
容易想到对于排序和村庄同时按照时间排序,对于每一个询问建好对应的村庄,暴力跑一轮 DJ 即可。
时间复杂度 \(O(nQ\log n)\) 可压线过。
然后数据保证了村庄修补好的时间是递增的,显然是在提示了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=209,M=50009;
int n,m,q,t[N],ans[M];
struct node{
int v,w;
bool operator <(const node &rhs)const{
return w>rhs.w;
}
};
priority_queue<node> pq;
vector<node> k[N],kk[N];
struct query{int x,y,t;} qr[M];
int dis[N],vis[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",t+i);
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w),++u,++v;
k[u].push_back({v,w});
k[v].push_back({u,w});
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
scanf("%d%d%d",&qr[i].x,&qr[i].y,&qr[i].t),++qr[i].x,++qr[i].y;
for(int i=1,j=1;i<=q;i++){
while(j<=n&&t[j]<=qr[i].t){
for(node x:k[j]) if(t[x.v]<=qr[i].t){
kk[x.v].push_back({j,x.w});
kk[j].push_back(x);
}
++j;
}
if(t[qr[i].x]>qr[i].t||t[qr[i].y]>qr[i].t)
ans[i]=-1;
else{
for(int i=1;i<=n;i++) dis[i]=1e9,vis[i]=0;
dis[qr[i].x]=0;
pq.push({qr[i].x,0});
while(!pq.empty()){
int tp=pq.top().v,tds=pq.top().w;
pq.pop();
if(vis[tp]) continue;
vis[tp]=1;
for(node x:kk[tp]) if(dis[x.v]>tds+x.w)
dis[x.v]=tds+x.w,pq.push({x.v,dis[x.v]});
}
ans[i]=(dis[qr[i].y]>1e8)?-1:dis[qr[i].y];
}
}
for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
return 0;
}
0x02
https://www.luogu.com.cn/problem/P4588
线段树板板题,考虑寻找每一个数字乘进来的时间和除掉的时间,相当是在答案数列的一段区间乘。
标记永久化即可。
0x03
https://www.luogu.com.cn/problem/P4079
数据范围略水,考虑构建 dfs 生成树,然后对于每一条返祖边不妨暴力跳跑质因数分解判断即可。
0x04
szj 找的题。
https://www.luogu.com.cn/problem/AT_arc081_c
就是建立一颗自动机,然后 dp,题并不困难。
0x05
https://www.luogu.com.cn/problem/P4302
那一刻,我想到了我学过的所有字符串算法……
然而 \(n\le 100\)。
考虑区间 dp。
一个区间有两种转移方式:
- 唐氏转移,直接合并。
- 折叠转移,折叠前最优折叠后必然最优,所以枚举因数,哈希判断周期,然后考虑折叠的问题。
0x06
https://www.luogu.com.cn/problem/P5522
n=30 这种东西,很明显想让你去状压成 int。
每个状态可以表示为:一些位确定的数字,确定的位都相同吗,如果相同的话是多少呢。
这东西整个线段树上去就完事了。
点击查看代码
#include<bits/stdc++.h>
#define ls (i<<1)
#define rs (i<<1|1)
#define ins int mid=(l+r)>>1
using namespace std;
const int N=35,M=100099;
int n,m,q,ta[M],tb[M],ans;
char s[N];
void go(int &a,int &b){
a=b=0;
for(int i=1;i<=n;i++){
a<<=1,b<<=1;
a|=(s[i]!='?');
b|=(s[i]=='1');
}
}
struct sgmt{
void pu(int i){
a[i]=a[ls]|a[rs];
ok[i]=(((b[ls]^b[rs])&(a[ls]&a[rs]))==0)&ok[ls]&ok[rs];
b[i]=b[ls]|b[rs];
//如果他这东西某一位是一,就是被标记掉了,要保存。
}
void bd(int i,int l,int r){
if(l==r){
a[i]=ta[l];
b[i]=tb[l];
ok[i]=1;
return;
}
ins;
bd(ls,l,mid),bd(rs,mid+1,r);
pu(i);
}
void mf(int i,int l,int r,int x,int aa,int bb){
if(l==r){
a[i]=aa,b[i]=bb,ok[i]=1;
return;
}
ins;
if(x<=mid) mf(ls,l,mid,x,aa,bb);
else mf(rs,mid+1,r,x,aa,bb);
pu(i);
}
void qr(int i,int l,int r,int ll,int rr,int &aa,int &bb,bool &okk){
if(l==ll&&r==rr){
aa=a[i],bb=b[i],okk=ok[i];
return;
}
ins;
if(rr<=mid) qr(ls,l,mid,ll,rr,aa,bb,okk);
else if(ll>mid) qr(rs,mid+1,r,ll,rr,aa,bb,okk);
else{
int aaa,aaaa,bbb,bbbb;
bool okkk,okkkk;
qr(ls,l,mid,ll,mid,aaa,bbb,okkk);
qr(rs,mid+1,r,mid+1,rr,aaaa,bbbb,okkkk);
aa=aaa|aaaa;
bb=bbb|bbbb;
okk=okkk&okkkk&((aaa&aaaa&(bbb^bbbb))==0);
}
}
int a[M<<2],b[M<<2];
bool ok[M<<2];
}T;
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++)
scanf("%s",s+1),go(ta[i],tb[i]);
T.bd(1,1,m);
while(q--){
int op,pos,l,r,a,b; bool ok;
scanf("%d",&op);
if(op==0){
scanf("%d%d",&l,&r);
T.qr(1,1,m,l,r,a,b,ok);
if(ok)
ans^=(1<<(n-__builtin_popcount(a)));//,printf("(%d)%d %d %d\n",a,ans,l,r);
}else{
scanf("%d%s",&pos,s+1);
go(a,b);
T.mf(1,1,m,pos,a,b);
}
}
printf("%d\n",ans);
return 0;
}
0x07
https://www.luogu.com.cn/problem/P5994
这题有点意思,不是一眼题。
dfs 爆搜党众所周知,攻择严,守择佳,
每次给你一段区间异或和。
发现 dp 什么的不好操作,还是需要想,一个合法的集合会是什么样子呢?
先猜一个重要结论:必然只选择 \(n\) 个东西进入集合。
我们先认为这是对的,然后呢?
你考虑什么样的东西是不优的。
你发现如果图论建模,每一个查询区间定义为一条边,有环一定是不优的。
这东西即使你倒着走,由于异或的性质这也是成立的。
自环查询这种你又该如何考虑呢?
由于这东西比较玄学,我们不妨用前缀异或和刻画这个东西。
你需要知道所有位置,每次查询等价于对两个点连条边,告诉你他相同或不相同。
然后你知道第零个位置的值,你就连 \(n\) 条边,都连成最小生成树即可。
让我想起了某次模拟赛 T1 一道 MST……
0x08
https://www.luogu.com.cn/problem/P3398
不妨使用 dfn 思想,每次判断另外两个点和这两个点 LCA 的位置关系,分讨一下不难。
0x09
https://www.luogu.com.cn/problem/P1979
考虑搜索的话我们状态只存特殊点位置和空格位置即可。
考虑每次我们的决策一定是让空格走最短路,走到特殊点的某个方向,然后交换特殊点和空格。
这样的话,状态数极限就是相对可控的。
然后考虑预处理,枚举特殊点位置和方格方向的单源最短路,时间和状态数都是 \(O(4n^4)\) 的。
这样子 bfs 就能快到起飞了。
然后……就过了……
NOIP 搜索数据送都不避人了吗……
点击查看代码
#include<bits/stdc++.h>
#define f(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
const int N=32,dx[4]={0,1,-1,0},dy[4]={1,0,0,-1};
//右下左上
int n,m,q,dis[4][N][N][N][N],mp[N][N];
struct node{int x,y,step;};
queue<node> qu;
struct Node{
int x,y,pos,step;
bool operator <(const Node &rhs)const{
return step>rhs.step;
}
};
bool vis[4][N][N];
int dds[4][N][N];
priority_queue<Node> qq;
int main(){
scanf("%d%d%d",&n,&m,&q);
f(i,1,n) f(j,1,m)
scanf("%d",&mp[i][j]);
f(_,0,3) f(i,0,31) f(j,0,31) f(l,0,31) f(r,0,31)
dis[_][i][j][l][r]=1e9;
f(i,1,n) f(j,1,m) if(mp[i][j]) f(_,0,3) {
int tx=i+dx[_],ty=j+dy[_];
if(mp[tx][ty]==0) continue;
dis[_][i][j][tx][ty]=0;
qu.push({tx,ty,0}),mp[i][j]=0;
while(!qu.empty()){
int x=qu.front().x,y=qu.front().y,step=qu.front().step;
qu.pop();
f(__,0,3){
int xx=x+dx[__],yy=y+dy[__];
if(mp[xx][yy]==0||dis[_][i][j][xx][yy]<=step+1) continue;
dis[_][i][j][xx][yy]=step+1,qu.push({xx,yy,step+1});
}
}
mp[i][j]=1;
}
// f(_,0,3) f(i,1,n) f(j,1,m) f(l,1,n) f(r,1,m) if(dis[_][i][j][l][r]<1e8)
// printf("lock (%d,%d) space (%d,%d) go (%d,%d) step %d\n",i,j,i+dx[_],j+dy[_],l,r,dis[_][i][j][l][r]);
f(i,1,q){
f(_,0,3) f(a,0,31) f(b,0,31)
dds[_][a][b]=1e9,vis[_][a][b]=0;
while(!qq.empty()) qq.pop();
int bx,by,sx,sy,ex,ey;
scanf("%d%d%d%d%d%d",&bx,&by,&sx,&sy,&ex,&ey);
if(sx==ex&&sy==ey){
puts("0");
continue;
}
f(_,0,3){
int step=dis[_][sx][sy][bx][by];
if(step<1e8){
qq.push({sx+dx[_],sy+dy[_],3-_,step+1});
dds[3-_][sx+dx[_]][sy+dy[_]]=step+1;
}
}
bool done=0;
while(!qq.empty()){
int x=qq.top().x,y=qq.top().y,pos=qq.top().pos,step=qq.top().step;
// printf("(%d,%d) %d %d\n",x,y,pos,step);
qq.pop();
if(x==ex&&y==ey){
printf("%d\n",step);
done=1;
break;
}
if(vis[pos][x][y]) continue;
vis[pos][x][y]=1;
f(_,0,3){
int xx=x+dx[_],yy=y+dy[_];
if(mp[xx][yy]==0) continue;
if(dds[3-_][xx][yy]<=step+1+dis[pos][x][y][xx][yy]) continue;
dds[3-_][xx][yy]=step+1+dis[pos][x][y][xx][yy];
qq.push({xx,yy,3-_,dds[3-_][xx][yy]});
}
}
if(!done) puts("-1");
}
return 0;
}
0x0a
https://www.luogu.com.cn/problem/CF821D
这是一个悲惨的故事。
这题做法可以直接 dj/01bfs+泛化距离,
但我选择了图论建模,
发现你可以跨一行,当前行,跨两行。
那你就对于行和列建两个虚点,
需要特殊处理虚点。
请欣赏世界名画:《黄题写出 102 行代码》
点击查看代码
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=90009;
int n,m,T,dis[N];
map<int,int> hang[N],lie[N],hh,ll;
struct node{int v,w;};
vector<node> k[N];
bool vis[N];
deque<int> q;
int main(){
scanf("%d%d%d",&n,&m,&T);
for(int i=1,x,y;i<=T;i++)
scanf("%d%d",&x,&y),hang[x][y]=i,lie[y][x]=i;
int cnt=T;
for(int i=1;i<=n;i++){
++cnt,++cnt;
k[cnt-1].pb({cnt,1});
int lst=0;
for(auto j:hang[i]){
if(!lst) lst=j.first;
else if(j.first-lst==1){
int tmp=hang[i][lst];
k[tmp].pb({j.second,0});
k[j.second].pb({tmp,0});
// printf("Link %d %d %d\n",i,j.first,lst);
}
k[j.second].pb({cnt-1,0});
k[cnt].pb({j.second,0});
lst=j.first;
}
if(i>1){
k[cnt-1].pb({cnt-2,1});
k[cnt-3].pb({cnt,1});
}
if(i>2){
k[cnt-1].pb({cnt-4,1});
k[cnt-5].pb({cnt,1});
}
}
for(int i=1;i<=m;i++){
++cnt,++cnt;
k[cnt-1].pb({cnt,1});
int lst=0;
for(auto j:lie[i]){
if(!lst) lst=j.first;
else if(j.first-lst==1){
int tmp=lie[i][lst];
k[tmp].pb({j.second,0});
k[j.second].pb({tmp,0});
}
k[j.second].pb({cnt-1,0});
k[cnt].pb({j.second,0});
lst=j.first;
}
if(i>1){
k[cnt-1].pb({cnt-2,1});
k[cnt-3].pb({cnt,1});
}
if(i>2){
k[cnt-1].pb({cnt-4,1});
k[cnt-5].pb({cnt,1});
}
}
int s=hang[1][1];
for(int i=1;i<=cnt;i++) dis[i]=1e9;
dis[s]=0,q.push_front(s);
while(!q.empty()){
int u=q.front();
q.pop_front();
if(vis[u]) continue;
vis[u]=1;
for(node it:k[u]) if(dis[it.v]>dis[u]+it.w){
dis[it.v]=dis[u]+it.w;
if(it.w) q.pb(it.v);
else q.push_front(it.v);
}
}
// for(int i=1;i<=n;i++) for(auto j:hang[i]) printf("%d %d %d\n",i,j.first,dis[j.second]);
if(hang[n].count(m)){
int t=hang[n][m];
printf("%d\n",(dis[t]>1e8)?-1:dis[t]);
}else{
int ans=1e9;
for(auto it:hang[n-1])
ans=min(ans,dis[it.second]+1);
for(auto it:hang[n])
ans=min(ans,dis[it.second]+1);
for(auto it:lie[m-1])
ans=min(ans,dis[it.second]+1);
for(auto it:lie[m])
ans=min(ans,dis[it.second]+1);
printf("%d\n",(ans>1e8)?-1:ans);
}
return 0;
}
0x0b
https://www.luogu.com.cn/problem/P3959
简单来说,给你个图,让你求一个最小生成树,还得自己决定根节点,
每条边边权定义为节点深度乘边的长度,
我们应当好好利用数据范围。
20 pts
枚举每一个点并进行 dfs 统计答案即可。
40 pts
你考虑这么个事情,我们建树按这道题来说,建出来必然是一棵 bfs 生成树……吗?
并非,参考样例。
但是你建 bfs 生成树过 40 分肯定还是没有问题的。
然后呢?
然后你考虑,每次你可以枚举一条边,有效边数也就 66 条。
这种东西,纯暴力是不能过掉的。
我想到了一种类似 Prim 最小生成树的做法。
就是按层枚举,枚举根节点,枚举哪些点作为其直系儿子。
这里优化一下,就是离得最近的若干个,必然成为儿子。
不知道道理在哪,但不妨写一发。

浙公网安备 33010602011771号