前言
又发现了许多需要学习的东西……
NO.1 BZOJ 4281 LCA(不知道叫什么名字 )
Description
给定一棵有\(n\)个点的无根树,相邻的点之间的距离为\(1\),一开始你位于\(m\)点。之后你将依次收到\(k\)个指令,每个指令包含两个整数\(d\)和\(t\),你需要沿着最短路在\(t\)步之内(包含\(t\)步)走到\(d\)点,如果不能走到,则停在最后到达的那个点。请在每个指令之后输出你所在的位置。
Input
第一行包含三个正整数\(n,m,k(1\le m\le n\le 1000000,1\le k\le 1000000)\)。
接下来\(n-1\)行,每行包含两个正整数\(x,y(1\le x,y\le n)\),描述一条树边。
接下来\(k\)行,每行两个整数\(d\),\(t\)\((1\le d\le n,0\le t\le 10^9)\),描述一条指令。
Output
输出一行,包含\(k\)个正整数,即执行每条指令后你所在的位置。
样例
样例输入
3 1 2
1 2
2 3
3 4
1 1
样例输出
3 2
分析
这个题原本的题目是\(LCA\),考试的时候就变成了”不知道叫什么名字 “,然后就……,其实就是求\(LCA\)的板子,就是有一些卡常,加一下快读快写就行。
我们每一次求出\(d\)和上一次位置的\(LCA\),然后找出两个点之间的距离,如果大于\(t\),那么分成两种情况:
1.上一次位置到最近公共祖先的距离大于\(t\),我们就可以直接从\(d\)向上走\(t\)个。
2.上一次位置到最近公共祖先距离小于\(t\),那么就是总距离减去\(t\),然后从\(d\)向上走减完的距离。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int f[maxn][23],dis[maxn],deep[maxn],head[maxn];
struct Node{
int v,next;
}e[maxn<<1];
int tot,n,m,d,t,k;
inline int read(){//快读
int s=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';
ch=getchar();
}
return s*f;
}
void Add(int x,int y){//建边
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
}
void dfs(int now,int fa){//求出每个点的深度
f[now][0]=fa;
deep[now]=deep[fa]+1;
for(int i=1;(1<<i)<=deep[now];i++){
f[now][i]=f[f[now][i-1]][i-1];
}
for(int i=head[now];i;i=e[i].next){
if(fa!=e[i].v) dfs(e[i].v,now);
}
}
int lca(int x,int y){//倍增求lca
int ans=0;
if(deep[x]>deep[y]) swap(x,y);
int len=deep[y]-deep[x],k=0;
while(len){
if(len & 1){
y=f[y][k];
}
++k;
len>>=1;
}
if(x==y) return x;
for(int i=20;i>=0;i--){
if(f[x][i]==f[y][i]) continue;
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int js(int x,int d){//计算向上走完能走的距离之后的位置
for(int i=20;~i;i--){
if((1<<i)<=d){
x=f[x][i];
d-=(1<<i);
}
}
return x;
}
void write(int x){//快写
if(x<0){
putchar('-');
x=-x;
}
if(x>9)
write(x/10);
putchar(x%10+'0');
}
int main(){
n=read();
m=read();
k=read();
int pre = m;
for(int i=1;i<n;++i){
int x=read();
int y=read();
Add(x,y);
Add(y,x);
}
dfs(1,1);
for(int i=1;i<=k;++i){
d=read();
t=read();
int z=lca(d,pre);
if(deep[pre]-deep[z]>=t)pre = js(pre,t);//从上个位置到lca的距离大于t
else if(deep[d]+deep[pre]-2*deep[z]<=t)pre = d;//能到达d点
else {
t=deep[d] + deep[pre]-2*deep[z]-t;//上个位置到lca距离小于t但是到不了d
pre = js(d,t);
}
write(pre);
printf(" ");
}
return 0;
}
NO.2 虫洞
题目描述
\(N\)个虫洞,M条单向跃迁路径。从一个虫洞沿跃迁路径到另一个虫洞需要消耗一定量的燃料和\(1\)单位时间。虫洞有白洞和黑洞之分。设一条跃迁路径两端的虫洞质量差为\(delta\)。
从白洞跃迁到黑洞,消耗的燃料值减少\(delta\),若该条路径消耗的燃料值变为负数的话,取为\(0\)。
从黑洞跃迁到白洞,消耗的燃料值增加\(delta\)。
路径两端均为黑洞或白洞,消耗的燃料值不变化。
作为压轴题,自然不会是如此简单的最短路问题,所以每过\(1\)单位时间黑洞变为白洞,白洞变为黑洞。在飞行过程中,可以选择在一个虫洞停留\(1\)个单位时间,如果当前为白洞,则不消耗燃料,否则消耗\(s_i\) 的燃料。现在请你求出从虫洞\(1\)到\(N\)最少的燃料消耗,保证一定存在\(1\)到\(N\)的路线。
输入格式
第\(1\)行:\(2\)个正整数\(N,M\)
第\(2\)行:\(N\)个整数,第\(i\)个为\(0\)表示虫洞\(i\)开始时为白洞,\(1\)表示黑洞。
第\(3\)行:\(N\)个整数,第\(i\)个数表示虫洞\(i\)的质量\(w_i\)。
第4行:\(N\)个整数,第\(i\)个数表示在虫洞i停留消耗的燃料\(s_i\)。
第\(5..M+4\)行:每行\(3\)个整数,\(u,v,k\),表示在没有影响的情况下,从虫洞\(u\)到虫洞\(v\)需要消耗燃料\(k\)。
输出格式
一个整数,表示最少的燃料消耗。
样例
样例输入
4 5
1 0 1 0
10 10 100 10
5 20 15 10
1 2 30
2 3 40
1 3 20
1 4 200
3 4 200
样例输出
130
样例解释
按照\(1\to 3\to 4\)的路线。
数据范围与提示
对于\(30\%\)的数据: \(1\le N\le 100,1\le M\le 500\)
对于\(60\%\)的数据: \(1\le N\le 1000,1\le M\le 5000\)
对于\(100\%\)的数据: \(1\le N\le 5000,1\le M\le 30000\)
其中\(20\%\)的数据为\(1\le N\le 3000\)的链
\(1\le u,v\le N, 1\le k,w_i,s_i\le 200\)
分析
因为黑白洞之间可以相互转化,所以我们用分层图处理,因为每一次走过之后都会白变黑,黑变白,所以如果是同种,就应该从黑到白,从白到黑建一个边,边权为输入的边权。因为可以等待,所以把每个点从白到黑连边,边权为\(0\),从黑到白也一样,但是边权应该是\(s_i\)。我们还需要建两边黑白不同的边,按照要求建边就行了,还是因为每一次走过之后都会白变黑,黑变白,所以虽然是两边不同,建边的时候还是要统一颜色建边,然后跑最短路就行了。
值得注意的是:如果开始是黑洞,那么应该从第二层开始最短路。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e4+10;
int head[maxn];
struct Node{
int v,next,val;
}e[maxn*10];
int tot;
int dis[maxn],vis[maxn],s[maxn],w[maxn],jl[maxn];
void Add(int x,int y,int val){//建边
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
e[tot].val = val;
}
priority_queue<pair<int,int> >q;
int n,m;
void Dij(int x){//最短路
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[x] = 0;
q.push(make_pair(0,x));
while(!q.empty()){
int y = q.top().second;
q.pop();
if(vis[y])continue;
vis[y] = 1;
for(int i=head[y];i;i=e[i].next){
int v=e[i].v;
if(dis[v]>dis[y]+e[i].val){
dis[v] = dis[y]+e[i].val;
q.push(make_pair(-dis[v],v));
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&jl[i]);
}
for(int i=1;i<=n;++i){
scanf("%d",&w[i]);
}
for(int i=1;i<=n;++i){
scanf("%d",&s[i]);
}
for(int i=1;i<=m;++i){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(jl[x] == jl[y]){//两边是同一个颜色,因为会变,所以应该到不同颜色建边
Add(x,y+n,z);//加n后是黑色
Add(x+n,y,z);
}
else {//两边颜色不同,简便要相同,按要求处理边权即可
int val = abs(w[x]-w[y]);
Add(x+n,y+n,z+val);
Add(x,y,max(z-val,0));
}
}
for(int i=1;i<=n;++i){//等待的时候简便
Add(i,i+n,0);
Add(i+n,i,s[i]);
}
if(jl[1] == 1){//开始是黑洞要从黑洞那一层开始
Dij(n+1);
}
else Dij(1);
cout<<min(dis[n],dis[2*n])<<endl;//取最小值
}
NO.3 图腾计数
题目描述
\(whitecloth\) 最近参观了楼兰图腾。图腾的所在地有一排\(N\)个柱子,\(N\)个柱子的高度恰好为一个\(1\)到\(N\)的排列,而楼兰图腾就隐藏在这些柱子中。
由于\(whitecloth\)弱爆了,他只知道图腾由\(3\)个柱子组成,这三个柱子组成了下凸或上凸的图形(>.<),所谓下凸,设三个柱子的高度从左到右依次为 \(h_1,h_2,h_3\),那么\(h_1>h_2,h_3>h_2\),上凸则满足\(h_1<h_2,h_3<h_2\)。
现在\(whitecloth\)也找不到图腾具体是哪三个柱子,他只想知道满足这两个形状的柱子有几组。
输入格式
第一行一个数\(𝑁\),接下来一行\(𝑁\)个数,依次表示每个柱子的高度
输出格式
一行两个数,表示下凸形状的数量和上凸形状的数量,用空格隔开
样例
样例输入
5
1 5 3 2 4
样例输出
3 4
数据范围与提示
\(𝑁\le 200000\)
分析
与昨天的题超级像,就是求逆序对,因为我归并还没调过,所以先分析线段树。
其实很简单,我们输入的时候保存一下每个点的坐标,然后先升序排序,线段树依次寻找当前左边和右边的比当前小的数量,然后记录下来。接着降序,,找到左边和右边比现在大的数量并记录。然后最后统计答案。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
#define ll long long
struct Node{
int l,r,sum;
ll mn,mx;
}t[maxn<<2];
struct N{
int wz;
ll hi;
}a[maxn];
ll sumlmin[maxn],sumrmin[maxn],sumlmax[maxn],sumrmax[maxn];
bool cmpxiao(N a,N b){
return a.hi<b.hi;
}
bool cmpda(N a,N b){
return a.hi>b.hi;
}
void pushup(int rt){
t[rt].mn = t[rt<<1].mn+t[rt<<1|1].mn;
t[rt].mx = t[rt<<1].mx+t[rt<<1|1].mx;
}
void Build(int rt,int l,int r){
t[rt].l = l;
t[rt].r = r;
if(l == r){
t[rt].mx = t[rt].mn = 0;
return;
}
int mid = (l+r)>>1;
Build(rt<<1,l,mid);
Build(rt<<1|1,mid+1,r);
pushup(rt);
}
void changemin(int rt,int pos,ll w){
if(t[rt].l == t[rt].r){
t[rt].mn+=w;
return;
}
int mid = (t[rt].l + t[rt].r)>>1;
if(pos<=mid)changemin(rt<<1,pos,w);
else changemin(rt<<1|1,pos,w);
pushup(rt);
}
void changemax(int rt,int pos,ll w){
if(t[rt].l == t[rt].r){
t[rt].mx+=w;
return;
}
int mid = (t[rt].l + t[rt].r)>>1;
if(pos<=mid)changemax(rt<<1,pos,w);
else changemax(rt<<1|1,pos,w);
pushup(rt);
}
ll checkmin(int rt,int l,int r){
if(t[rt].l >= l && t[rt].r <= r){
return t[rt].mn;
}
ll ans=0;
int mid = (t[rt].l+t[rt].r)>>1;
if(l<=mid)ans+=checkmin(rt<<1,l,r);
if(r>mid)ans+=checkmin(rt<<1|1,l,r);
return ans;
}
ll checkmax(int rt,int l,int r){
if(t[rt].l >= l && t[rt].r <= r){
return t[rt].mx;
}
ll ans=0;
int mid = (t[rt].l+t[rt].r)>>1;
if(l<=mid)ans+=checkmax(rt<<1,l,r);
if(r>mid)ans+=checkmax(rt<<1|1,l,r);
return ans;
}
//上边都是线段树板子
int n;
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i].hi;
a[i].wz = i;
}
Build(1,1,n);
sort(a+1,a+n+1,cmpxiao);//升序排序
for(int i=1;i<=n;++i){//查找每个点左右比他矮的
sumlmin[a[i].wz] = checkmin(1,1,a[i].wz);
sumrmin[a[i].wz] = checkmin(1,a[i].wz,n);
changemin(1,a[i].wz,1);
}
sort(a+1,a+n+1,cmpda);//降序排序
for(int i=1;i<=n;++i){//查找每个点左右比他高的
sumlmax[a[i].wz] = checkmax(1,1,a[i].wz);
sumrmax[a[i].wz] = checkmax(1,a[i].wz,n);
changemax(1,a[i].wz,1);
}
ll ansmin=0,ansmax=0;
for(int i=1;i<=n;++i){
ansmin += sumlmin[a[i].wz]*sumrmin[a[i].wz];//左右的比当前小的乘起来
ansmax += sumlmax[a[i].wz]*sumrmax[a[i].wz];//同上
}
cout<<ansmax<<" "<<ansmin<<"\n";
return 0;
}
NO.4 十字绣
题目描述
考古学家发现了一块布,布上做有针线活,叫做“十字绣”,即交替地在布的两面穿线。
布是一个\(n\times m\)的网格,线只能在网格的顶点处才能从布的一面穿到另一面。每一段线都覆盖一个单位网格的两条对角线之一,而在绣的过程中,一针中连续的两段线必须分处布的两面。并且每一段线只能走一次。
给出布两面的图案,问最少需要几针才能绣出来?一针是指针不离开布的一次绣花过程。
输入格式
第\(1\)行两个数\(N\)和\(M\)。
接下来\(N\)行每行\(M\)个数描述正面。
再接下来\(N\)行每行\(M\)个数描述反面。
每个格子用.(表示空),/(表示从右上角连到左下角),\(表示从左上角连到右下角)和X(表示连两条对角线)表示。
输出格式
一个数,最少要用的针数。
样例
样例输入
4 5
.....
....
....
.....
.....
....\
.\X..
.....
样例输出
4
(为使输入数据更直观,样例为图片格式,下面是文本格式的样例)
数据范围与提示
对于\(100\%\)的数据,\(1\le n,m\le 200\)
分析
刚一看到这个题,第一个想法就是建好图,然后\(Tarjan\)求联通分量,但是这么做肯定是不对的。
我们分析一下,如果一个联通块里有两个正面的线,一个反面的线,那么我们能够推出来这个联通块需要两针才行。在建图的时候,我们把这个字符组成的一个个格子都变成一个点,每个点的标号不同,然后建边
从这个结论可以判断,每一个联通块里的点都有其相对应的最小针数,而这个最小针数就是正面的线和反面的线数量差的绝对值,因为每个边的两个点都会计算,所以最后统计出来的答案应该除以\(2\)。
应该注意的一点就是最后如果统计出来的答案是\(0\),我们可以认为是环状,那么应该是有一针的,这里不用除以\(2\),所以直接让\(ans\)加\(2\)即可
最后需要注意的就是'\'这个符号,直接打出来肯定是不行的,我们可以把它直接放到最后直接\(else\),也可以'\ \',还有用\(ASCII\)码,值为92来判断。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
struct Node{
int v,next;
}e[maxn<<4];
bool vis[maxn],b[maxn];
char s[205][205];
int n,m,a[550][550],z[maxn<<1],num;
int ans1,ans2,tot,head[maxn<<1];
void Add(int x,int y,int data){//建边,如果正面就让data为0,这个点的度++,反之则--,我们就在这里求出了答案所需要的绝对值
if(data == 0)z[x]++;
else z[x]--;
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
}
void Dfs(int x){//深搜找每个联通块的针数
vis[x] = 1;
ans2+= abs(z[x]);
for(int i=head[x];i;i=e[i].next){
if(!vis[e[i].v])Dfs(e[i].v);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n+2;++i){
for(int j=1;j<=m+2;++j){
a[i][j] = ++num;
}
}
for(int i=1;i<=n;++i){//正面的所有边
cin>>s[i]+1;
for(int j=1;j<=m;++j){//建边的时候正面的data也就是边权为0,便于判断
if(s[i][j] == '\\'){//建边
Add(a[i][j],a[i+1][j+1],0);
Add(a[i+1][j+1],a[i][j],0);
b[a[i][j]] = b[a[i+1][j+1]] = 1;//标记这里有线
}
if(s[i][j] == '/'){
Add(a[i][j+1],a[i+1][j],0);
Add(a[i+1][j],a[i][j+1],0);
b[a[i][j+1]]=b[a[i+1][j]]=1;//同上
}
if(s[i][j]=='X'){//X则对角建边
Add(a[i][j+1],a[i+1][j],0);
Add(a[i][j],a[i+1][j+1],0);
Add(a[i+1][j],a[i][j+1],0);
Add(a[i+1][j+1],a[i][j],0);
b[a[i][j]]=b[a[i+1][j]]=b[a[i+1][j+1]]=b[a[i][j+1]]=1;
}
}
}
for(int i=1;i<=n;++i){//反面的建边,以下的细节同上
cin>>s[i]+1;
for(int j=1;j<=m;++j){
if(s[i][j] == '\\'){
Add(a[i][j],a[i+1][j+1],1);
Add(a[i+1][j+1],a[i][j],1);
b[a[i][j]] = b[a[i+1][j+1]] = 1;
}
if(s[i][j] == '/'){
Add(a[i][j+1],a[i+1][j],1);
Add(a[i+1][j],a[i][j+1],1);
b[a[i][j+1]]=b[a[i+1][j]]=1;
}
if(s[i][j]=='X'){
Add(a[i][j+1],a[i+1][j],1);
Add(a[i][j],a[i+1][j+1],1);
Add(a[i+1][j],a[i][j+1],1);
Add(a[i+1][j+1],a[i][j],1);
b[a[i][j]]=b[a[i+1][j]]=b[a[i+1][j+1]]=b[a[i][j+1]]=1;
}
}
}
for(int i=1;i<=num;++i){
if(b[i] && !vis[i]){//当前点没有访问过且有线
ans2=0;
Dfs(i);//深搜求联通块的答案
if(!ans2)ans1+=2;//联通块答案为0,也就是环,因为一会要除以2,所以直接加上2
else ans1 += ans2;
}
}
cout<<ans1/2<<"\n";
}
来源:oschina
链接:https://my.oschina.net/u/4353003/blog/4339922