T1:
样例输入:
7 15 5 2 3 12 4 5 10 0 0 21 0 0 15 6 7 8 0 0 23 0 0
样例输出:
4
题解:
对于一切二叉树的题目,我就不要用链式前项星来存图,直接对于对于每一个点,存储它的左儿子和右儿子,这样方便跑图并且可以保证先序/中序/后序输出的顺序的正确性
#include<cstdio>
using namespace std;
struct Node{
int l,r;
}tr[10010];
int Head[10010];
int kok;
int n,pod;
int val[10010];
int x,y;
int cnt,ord[10010];
int ck[10010];
int css[10010];
inline void dfs(int now,int fath)
{
if(tr[now].l!=0){
dfs(tr[now].l,now);
}
if(tr[now].l==0 || css[tr[now].l]!=0){
cnt++;
ord[cnt]=now;
css[now]=1;
}
if(tr[now].r!=0){
dfs(tr[now].r,now);
}
}
int main(void)
{
scanf("%d%d",&n,&pod);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&val[i],&x,&y);
tr[i].l=x;
tr[i].r=y;
}
dfs(1,0);
//for(int i=1;i<=n;i++)printf("%d ",val[ord[i]]);
for(int i=1;i<=n;i++){
if(val[ord[i]]==pod){
printf("%d",i);
return 0;
}
}
}
T2:
样例输入:
8 7 4 1 4 2 1 3 1 5 2 6 2 7 2 8
样例输出:
4 2 6 7 8
题解:
这道题没有什么坑点,破题的关键就是照着题目说的做,不要遗漏稍微细心一点就不会错,注意输出要排序和换行(找不到说什么了)
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{
int v,nxt;
}tr[10010];
int Head[10010];
int kok;
int n,m;
int x,y;
int root;
int maxx,num;
int cnt[10010];
int size[10010],son[10010],kl,fa[10010];
inline void add(int u,int v)
{
kok++;
tr[kok].v=v;
tr[kok].nxt=Head[u];
Head[u]=kok;
}
inline void dfs(int now,int fath)
{
fa[now]=fath;
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(v==fath)continue;
size[now]++;
dfs(v,now);
}
}
int main(void)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
cnt[y]=1;
}
for(int i=1;i<=n;i++){
if(cnt[i]==0)root=i;
}
dfs(root,0);
maxx=0;
for(int i=1;i<=n;i++){
if(maxx<size[i]){
num=i;
maxx=size[i];
}else{
if(maxx==size[i])
num=min(num,i);
}
}
kl=0;
for(int i=Head[num];i;i=tr[i].nxt){
int v=tr[i].v;
if(v==fa[num])continue;
kl++;son[kl]=v;
}
sort(son+1,son+kl+1);
printf("%d\n",root);
printf("%d\n",num);
for(int i=1;i<=kl;i++)printf("%d ",son[i]);
}
T3:
样例输入:
1 0 0 0 0 10000 10000 5000 -10000 5000 10000 5000 10000 10000 10000
样例输出:
3:55
题解:
这道题要吸取血的教训,题目保证了每条路之间会相互连接,所以直接以扫雪的速度把每条路走两遍(双向边)的时间就是最后的答案,注意要时间的换算
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
double fun(int a,int b,int c,int d)
{
long double x,y;
x = abs(a - c);
y = abs(b - d);
return sqrt(x * x + y * y) / 10000;
}
int main()
{
int x, y;
while(cin >> x >> y)
{
int i, n, x1, y1, x2, y2;
double sum = 0;
cin >> n;
for(i = 1; i <= n; i ++)
{
cin >> x1 >> y1 >> x2 >> y2;
sum += fun(x1,y1,x2,y2);
}
int T = sum - 1;
double t = sum - T;
int te =floor(t * 60 + 0.5);
if(te >= 60)
{
T ++;
te -= 60;
}
cout << T << ":";
if(te < 10) cout << "0";
cout << te << endl;
}
return 0;
}
T4:
样例输入:
4 3 1 2 1 2 2 3 1 4
样例输出:
5
题解:
样例解释它有大问题!!!
由于这令人难受的数据大小,所以用DFS的话会让栈溢出,直接爆炸,所以得吸取教训,每次在求图的深度的时候,先得看清楚边的个数(这道题没给)超过1e6的话就可能出现栈溢出的情况,所以就得用BFS。(BFS、DFS都得熟练)
这道题的关键就是不能把糖果数量量化,要不然会让自己深陷在读不懂题目的深渊之中,所以将c作为根跑BFS,求出每个节点的深度,最大深度+m就是本题的答案。
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<string>
using namespace std;
struct Node{
int v,nxt;
}tr[2200100];
int Head[2200100];
int kok;
int n,p,c;
long long m;
int x,y;
long long dep[2200100];
long long maxx=0;
bool vis[2200100];
queue<int>q;
inline void add(int u,int v)
{
kok++;
tr[kok].v=v;
tr[kok].nxt=Head[u];
Head[u]=kok;
}
inline void BFS()
{
memset(vis,0,sizeof(vis));
q.push(c);
dep[c]=1;
maxx=1;
while(!q.empty()){
int now=q.front();q.pop();
vis[now]=true;
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(vis[v]==true)continue;
vis[v]=true;
dep[v]=dep[now]+1;
maxx=max(dep[v],maxx);
q.push(v);
}
}
}
int main(void)
{
scanf("%d%d%d",&n,&p,&c);
scanf("%lld",&m);
for(int i=1;i<=p;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
BFS();
printf("%lld\n",maxx+m);
}
T5:
样例输入:
5 2 4 3 0 4 5 0 0 0 1 0
样例输出:
1
题解:
AOJ能过luogu过不了的算法:建立单项边,从入度为零的点开始DFS染色,再跑一遍DFS,从未染色的点开始跑,直到所有点染色。答案就是颜色的种类。
正解是:tarjan
复个习:PrincessYR✨~
有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。——百度百科
Tarjan是基于迪法师(DFS)的一种算法,可以说是一种流(du)批(liu)的操作,本蒟蒻苦哈哈的学了好几天才学会强连通分量和缩点;
在这里推荐给大家几道题:受欢迎的牛,消息扩散; 这些题我会在后期的博客中进行详细的讲解
步入正轨:
图中{6}是第一个被发现的强连通分量,其次是{5},最后被发现的是{3,4,1,2}(顺序无所谓)
Tarjan算法是解决强连通分量和缩点问题的一种比较常见的方法
接下来开始????一步一步的 首先给你一个图(求其中的强连通分量)
在这里我们假设从点1开始遍历,并且将访问的点压入栈中
在进行操作之前,我们要明确Tarjan中的两个至关重要的数组:DFN和LOW;
DFN[x]:表示这个点x几次被遍历到;
LOW[x]:表示点x或点x的子树能够追溯到的最早的栈中节点的次序号;
即LOW[x]=min{LOW[x],LOW[next]};
其中next为x的子节点;
接下来,我们来一步一步的找;
第一遍从1直接一直遍历到6,另访问到的点的初始low和dfn都为num++;num为计数器;
但当我们遍历到6是发现6没有子节点了,所以low[6]=dfn[6]=4;发现一个强连通分量{6},6出栈;
退回到5,low[5]=min(low[5],low[6]);
low[6]=4;而low[5]=dfn[5]=3;
所以low[5]=3;
low[5]=dfn[5];所以5也是一个强连通分量;5出栈;
返回3发现3有子节点4,搜索4,并且4入栈;发现4也有子节点,6已经为强连通分量且不再栈中,跳过;
遍历1,发现一在栈中且已经被访问过,那么4值得被发现;
所以low[4]=min(low[4],dfn[1]);所以low[4]=1;
4遍历完,又因为dfn[4]!=low[4],所以4不退站;回到3,low[3]=min(low[3],low[4]);
所以low[3]=1;3访问完,又因为dfn[3]!=low[3],所以3不退站;
回到1,发现1还有子节点2,2入栈;发现2的子节点4已经被访问且在栈中,所以low[2]=min(dfn[4],low[2]);
所以low[2]=5;访问完2,回到一,发现low[1]=dfn[1];所以将栈中的所有点出栈,并且这些点为一个强连通分量;
至此,所有点已经都被访问,算法结束。
时间复杂度为O(N+M);
AOJ A码,luogu WA码
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
struct Node{
int v,nxt;
}tr[10010];
int Head[10010];
int kok;
int n;
int x;
int col[10010];
int cnt[10010];
int ans;
queue<int>q;
inline void add(int u,int v)
{
kok++;
tr[kok].v=v;
tr[kok].nxt=Head[u];
Head[u]=kok;
}
inline void dfs(int now,int fath)
{
if(col[now]!=0)return ;
col[now]=col[fath];
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(col[v]!=0)continue;
dfs(v,now);
}
}
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
while(1){
scanf("%d",&x);
if(x==0)break;
add(i,x);
cnt[x]++;
}
}
ans=0;
for(int i=1;i<=n;i++){
if(cnt[i]==0){
col[0]=++ans;
dfs(i,0);
}
}
for(int i=1;i<=n;i++){
if(col[i]==0){
col[0]=++ans;
dfs(i,0);
}
}
printf("%d",ans);
}
AC码
#include<iostream>
#include<cstdio>
#include<vector>
#include<stack>
#define si 205
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int n,a,tot,num,bl[si],ru[si],nums[si],numb,dfn[si],low[si],ins[si];
vector<int> map[si];
stack<int> st;
void tarjan(int x)
{
dfn[x]=low[x]=++num;
ins[x]=1;
st.push(x);
for(int i=0;i<map[x].size();i++)
{
int q=map[x][i];
if(dfn[q]==0)
{
tarjan(q);
low[x]=min(low[x],low[q]);
}else if(ins[q]==1)
{
low[x]=min(low[x],dfn[q]);
}
}
if(dfn[x]==low[x])
{
numb++;
int p;
do
{
p=st.top();
st.pop();
ins[p]=0;
bl[p]=numb;
nums[numb]++;
}while(x!=p);
}
}
int main()
{
n=read();
a=1;
for(int i=1;i<=n;i++)
{
while(a!=0)
{
a=read();
if(a==0)
continue;
map[i].push_back(a);
}
a=1;
}
for(int i=1;i<=n;i++)
{
if(dfn[i]==0)
{
tarjan(i);
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<map[i].size();j++)
{
int next=map[i][j];
if(bl[i]!=bl[next])
{
ru[bl[next]]++;
}
}
}
for(int i=1;i<=numb;i++)
{
if(ru[i]==0)
tot++;
}
cout<<tot;
return 0;
}
T6:
样例输入:
4 6 22 10 20 4 18 6 16 8 20 2 18 10 24 4 8 9 15 19 17 11 7 21 11
样例输出:
A 4 B 1 C 2 D 3
题解:
啊这道题正解非常好想,把能够匹配这个ppt的所有数码统计一下,即计算每个ppt的入度,然后每次找到入度为1的ppt,将其匹配并且记录答案,然后把每个能够被此数码匹配的ppt入度减一,再重复找入度为一的ppt,如此反复,求出答案,当出现余下的ppt入度都大于一的时候,则输出None
#include<cstdio>
using namespace std;
struct Node1 {
int xmin,xmax;
int ymin,ymax;
int in;
int ans;
} tr[100];
struct Node2 {
int x,y;
int vis;
} cd[100];
int n,pos;
int main(void) {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d%d%d%d",&tr[i].xmin,&tr[i].xmax,&tr[i].ymin,&tr[i].ymax);
tr[i].in=0;
}
for(int i=1; i<=n; i++) {
scanf("%d%d",&cd[i].x,&cd[i].y);
cd[i].vis=0;
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
if(tr[j].xmin<=cd[i].x && tr[j].xmax>=cd[i].x &&
tr[j].ymin<=cd[i].y && tr[j].ymax>=cd[i].y) {
tr[j].in++;
}
}
}
int T=n;
int check=0;
while(T--) {
pos=-1;
check=0;
for(int i=1;i<=n;i++)if(tr[i].in==0)check++;
if(check==n)break;
for(int i=1; i<=n; i++) {
if(tr[i].in==1) {
pos=-1;
for(int j=1; j<=n; j++) {
if(tr[i].xmin<=cd[j].x && tr[i].xmax>=cd[j].x &&
tr[i].ymin<=cd[j].y && tr[i].ymax>=cd[j].y &&
cd[j].vis==0) {
pos=j;
break;
}
}
if(pos==-1 && tr[i].ans!=0){
printf("None");
return 0;
}
cd[pos].vis=1;
tr[i].ans=pos;
tr[i].in--;
for(int j=1; j<=n; j++) {
if(tr[j].xmin<=cd[pos].x && tr[j].xmax>=cd[pos].x &&
tr[j].ymin<=cd[pos].y && tr[j].ymax>=cd[pos].y &&
tr[j].in>1)
tr[j].in--;
}
}
}
if(pos==-1){
printf("None");
return 0;
}
}
for(int i=1;i<=n;i++)
if(tr[i].in!=0){
printf("None");
return 0;
}
for(int i=1;i<=n;i++){
putchar((int)('A'+i-1));
printf(" %d\n",tr[i].ans);
}
}
T7:
样例输入:
4 1 2 1 1 3 1 1 4 2
样例输出:
12
题解:
这就是昨天的泼水节传送门直接上码
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{
int u,v;
long long w;
}tr[2000010];
int n;
int fa[2000010];
long long size[2000010];
int tx,ty;
long long ans;
inline bool tmp(Node xx,Node yy){return xx.w<yy.w;}
inline int find(int x)
{
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
int main(void)
{
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d%lld",&tr[i].u,&tr[i].v,&tr[i].w);
}
for(int i=1;i<=n;i++)fa[i]=i,size[i]=1;
sort(tr+1,tr+n,tmp);
ans=0ll;
for(int i=1;i<n;i++)ans+=tr[i].w;
for(int i=1;i<n;i++){
tx=find(tr[i].u);
ty=find(tr[i].v);
if(tx==ty)continue;
ans+=(long long)1ll*(tr[i].w+1ll)*(size[tx]*size[ty]-1ll);
size[tx]+=size[ty];
fa[ty]=tx;
}
printf("%lld",ans);
}
T8:
样例输入:
5 3 7 3 8 10 3 6 8 1 1 3 1 10 11 1
样例输出:
6
题解:
本来是考 差分约束系统传送门
法一:
差分约束系统蒟蒻zunny
之前那篇题解的输出好像有点问题,但思想是没有任何问题的。
此题是差分约束的裸题,但我之前也不知道差分约束系统是什么。
定义:差分约束系统是一种特殊的N元一次不等式,包含N个变量及M个约束条件,每个约束条件由两个变量作差构成,形如Xi-Xj>=Ck,Ck是常数,1<=i,j<=N,1<=k<=M。如
X1-X0>=2
X2-X1>=3
X3-X2>=2
X3-X0>=6
通过消元可得到X3-X0的最小值为7。假若我们从0->1建一条权值为2的边,从1->2建一条权值为3的边,以此类推。从0到3跑一遍SPFA求最长路,发现也为7。
这不是个巧合,因为求最长路时,SPFA的松弛操作后,dis[v]>=dis[u]+g[i].w,与不等式Xi>=Xj+Ck性质一样,所以SPFA可以求出最小值,又因为不等式同大取大,所以要求出最长路。所以SPFA死了吗?
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
int t,n,head[500005],tot,ma,dis[500005];
bool vis[500005];
struct node{
int u,v,next,w;
}g[5000005];
inline int rea(){//快读
int s=0,g=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
g=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<3)+(s<<1)+(ch^48);
ch=getchar();
}
return s*g;
}
inline void add(int u,int v,int w){//建图
g[++tot].u=u;
g[tot].v=v;
g[tot].w=w;
g[tot].next=head[u];
head[u]=tot;
}
inline void spfa(){//SPFA求最长路,即可得出最小值
queue<int>q;
q.push(0);
vis[0]=true;
dis[0]=0;
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=head[u];i;i=g[i].next){
int v=g[i].v;
if(dis[v]<dis[u]+g[i].w){
dis[v]=dis[u]+g[i].w;
if(!vis[v]){
vis[v]=true;
q.push(v);
}
}
}
}
}
int main(){
t=rea();
while(t--){
memset(head,0,sizeof(head));
memset(dis,-0x3f,sizeof(dis));
tot=0;
n=rea();
for(int i=1;i<=n;i=-~i){
int a=rea(),b=rea(),c=rea();
add(a,b+1,c);//因为前缀和中,Sb-S(a-1)>=c,然而a-1要大于等于0,a有可能是0,所以我们加一
ma=max(ma,b+1);
}
for(int i=1;i<=ma;i=-~i){
add(i-1,i,0);
add(i,i-1,-1);//Si-S(i-1)>=0,S(i-1)-Si>=-1
}
spfa();
printf("%d\n",dis[ma]);
if(t)
printf("\n");//注意输出!!!
}
return 0;
}
法二:
全网唯一线段树题解!
(差分约束?溜了溜了)
有一种显然的贪心, 就是将每一个区间按照右端点升序排序。那么对于每一段区间的回答,从左往右依次遍历:如果在当前区间内已选的数大于等于需求,我们进行下一个操作;如果小于,那么一定选是靠右边的数,因为这样才会对接下来的策略有最优影响。
统计区间内已选的数很好办,用线段树维护就行了。同时因为它选的是一段连续的靠右序列,刚好可以用线段树进行修改,直接将该目标区间的 val_pvalp 赋为 r_p - l_p + 1rp−lp+1 就行了,其中 val_pvalp 、l_plp 、 r_prp 表示左端为 l_plp , 右端为 r_prp 的线段树节点 pp 含有数的总数为 val_pvalp 。
但是区间缺少的靠右的数未必集中在最右边,不免有右端放了许多数而不得不用选用左端数的情况。这时候我们就二分查找,看从 midmid 到最右端缺少的数的数量是否大于等于实际区间缺少数量,不断找到最优策略。
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cmath>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
inline int read(){
int x=0,w=0;char ch=getchar();
while (!isdigit(ch))w|=ch=='-',ch=getchar();
while (isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return w?-x:x;
}
struct node{
int x, y, z;
bool operator <(const node &xx)const{
return xx.y == y ? xx.x > x : xx.y > y;
}
}b[50005];
int t, n, ans, tot;
int N;
struct Segment_Tree{
int val, tag, sum;
}a[1000005];
// 线段树基本操作
inline int ls(int p){
return p << 1;
}
inline int rs(int p){
return p << 1 | 1;
}
inline void update(int p){
a[p].val = a[ls(p)].val + a[rs(p)].val ;
}
inline void push_up(int p, int l, int r){
a[p].tag ++;
a[p].val = r - l + 1;
}
inline void push_down(int p, int l, int r){
if(!a[p].tag ) return ;
int mid = l + r >> 1;
push_up(ls(p), l, mid);
push_up(rs(p), mid + 1, r);
a[p].tag = 0;
}
inline void build(int p, int l, int r){
if(l == r){
a[p].tag = a[p].val = 0;
return ;
}
int mid = l + r >> 1;
build(ls(p), l, mid);
build(rs(p), mid + 1, r);
a[p].tag = a[p].val = 0;
}
inline void modify(int p, int l, int r, int L, int R){
if(L <= l && r <= R){
a[p].val = r - l + 1;
a[p].tag = 1;
return ;
}
push_down(p, l, r);
int mid = l + r >> 1;
if(L <= mid) modify(ls(p), l, mid, L, R);
if(mid < R) modify(rs(p), mid + 1, r, L, R);
update(p);
}
inline int query(int p, int l, int r, int L, int R){
if(L <= l && r <= R){
return a[p].val ;
}
push_down(p, l, r);
int mid = l + r >> 1, res = 0;
if(L <= mid) res += query(ls(p), l, mid, L, R);
if(mid < R) res += query(rs(p), mid + 1, r, L, R);
update(p);
return res;
}
int main(){
t = read();
while(t--){
n = read();
N = 0;
ans = 0;
for(int i = 1; i <= n; i++){
b[i].x = read() + 1, b[i].y = read() + 1, b[i].z = read();
N = max(N, b[i].y );
}
sort(b + 1, b + n + 1);
build(1, 1, N);//重建树以清零
for(int i = 1; i <= n; i++){
int w = query(1, 1, N, b[i].x, b[i].y );
if(w >= b[i].z ) continue;
ans += b[i].z - w;
int l = b[i].x , r = b[i].y , ok = b[i].x , delta = b[i].z - w;
while(l <= r){//二分查找
int mid = l + r >> 1;
w = b[i].y - mid + 1 - query(1, 1, N, mid, b[i].y);
if(w >= delta ){
ok = mid;
l = mid + 1;
}
else {
r = mid - 1;
}
}
modify(1, 1, N, ok, b[i].y);
}
if(t)//注意UVA题目的输出格式
printf("%d\n\n",ans);
else printf("%d\n",ans);
}
}
总结:
图整完了需要重吸收,再复习