可达暑期集训笔记:CSP-S 沃思班

Day 1

并查集

并查集是一种可以动态维护若干个不相交集合,并支持合并和查询的数据结构。

int f[_mxn];

//初始化
for(int i=1;i<=n;i++)
	f[i]=i;

//查找
int find(int x)
{
	if(f[x]==x)
		return x;
	return f[x]=find(f[x]);//路径压缩
}

//合并
int merge(int x,int y)
{
	f[find(y)]=find(x);
}

Day 1 模拟赛

A

题意

给定 \(n,k(n\le200,k\le10)\),求满足以下所有条件的序列 \(a\) 的个数:

  • \(a_{1}+a_{2}+a_{3}+\cdots+a_{k}=n\)
  • \(a_{1}\le a_{2}\le a_{3}\le\cdots\le a_{k}\)
  • \(a_{i}\) 均为质数。

题解

数据范围那么点,所以直接打一个 \(200\) 以内的质数表,然后爆搜就行了。但是有点卡常,所以需要一点点优化。

代码:

const int _mxk=10+5;
const int pri[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211};//质数表
int n,k;
int a[_mxk],ans=0;
bool ispri(int x)//判质数
{
    if(x<2)
        return false;
    for(int i=2;i*i<=x;i++)
        if(x%i==0)
            return false;
    return true;
}
void dfs(int dep,int t,int s)
{
    if(dep>=k)
    {
        if(ispri(n-s)&&a[k-1]<=n-s)//优化 1:少搜一个数,直接算出最后一个数,判断是否满足条件
            ans++;
        return;
    }
    for(int i=t;pri[i]<=n;i++)
    {
        a[dep]=pri[i];
        s+=a[dep];//实时算和
        if(s>n)//优化 2:和已经超过 n 了,不往下搜了
            break;
        dfs(dep+1,i,s);
        s-=a[dep];//回溯
    }
}
int main()
{
    ___();
    cin>>n>>k;
    dfs(1,0,0);
    cout<<ans<<endl;
    return 0;
}

B

题意

给你一张无向图,判断对于图上任意两个顶点之间是否有且仅有一条简单路径连接。

题解

不难发现,只要图上有环,那么肯定有不止一条简单路径;并且当图不连通时,两个不同连通分量上的点无法互相到达。所以只需要判图是否连通和图上是否有环即可。

代码:

const int _mxn=1e5+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
bool vis[_mxn];
bool dfs(int u,int fa)//判环
{
    vis[u]=true;
    bool res=false;
    for(int it:g[u])
    {
        if(it==fa)
            continue;
        if(vis[it])//走到走过的点,且不是直接返回
            return true;//有环
        res=res||dfs(it,u);//往下搜
    }
    return res;
}
//并查集部分,用于判连通(直接套的模板,所以有些没用的东西)
int f[_mxn],siz[_mxn];
void init(int n)
{
    for(int i=1;i<=n;i++)
        f[i]=i,siz[i]=1;
}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void merge(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)
    {
        f[fy]=fx;
        siz[fx]+=siz[fy];
    }
}
bool same(int x,int y){return find(x)==find(y);}
int size(int x){return siz[find(x)];}
int main()
{
    ___();
    int n=0;
    while(1)
    {
        int u,v;
        cin>>u>>v;
        if(u==-1&&v==-1)//输入结束
            break;
        if(u==0&&v==0)//一组数据结束
        {
            int cnt=0;//计算连通分量个数,大于 1 就代表图不连通
            for(int i=1;i<=n;i++)
                if(find(i)==i)
                    cnt++;
            cout<<(cnt>1||dfs(n,-1)?"No":"Yes")<<endl;//不连通或有环就不满足,否则满足,输出答案
            //多测记得清零
            for(int i=1;i<=n;i++)
                g[i].clear();
            init(n);
            n=0;
            memset(vis,false,sizeof(vis));
        }
        else
        {
            add(u,v),add(v,u);//加边
            merge(u,v);//并查集里合并
            n=max(n,max(u,v));
        }
    }
    return 0;
}

C

题意

假设一种昆虫有两种性别,并且只会与异性交往,给定昆虫的交往列表,判断该假设是否成立。

题解

有很像的一道原题 ABC327D

将两个交往昆虫之间连一条边,很明显,这就是一个二分图判定。只需要跑一遍 bfs 染色就行了。注意图可能不连通,所以要把每个连通分量都染一遍。

代码:

const int _mxn=2000+5;
int n,m;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int f[_mxn];//染色,为 -1 表示没染
bool bfs(int s)
{
    queue<int> q;
    q.push(s);
    f[s]=1;//设起点为 1
    while(!q.empty())
    {
        int nw=q.front();q.pop();
        for(int it:g[nw])
        {
            if(f[it]==-1)//没染
                f[it]=(f[nw]+1)%2,q.push(it);//染上不相同的颜色,并入队
            else
            {
                if(f[nw]==f[it])//染过就判两边是否相同,相同就不成立
                    return false;
            }
        }
    }
    return true;
}
int main()
{
    ___();
    int _;
    cin>>_;
    for(int ii=1;ii<=_;ii++)
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)//多测清零
            g[i].clear(),f[i]=-1;
        while(m--)
        {
            int u,v;
            cin>>u>>v;
            add(u,v),add(v,u);
        }
        bool fl=true;
        for(int i=1;i<=n;i++)
        {
            if(f[i]==-1)//没染过就把这个连通分量染一下
                fl=(fl&&bfs(i));
        }
        printf("Scenario #%d:\n",ii);
        cout<<(fl?"No suspicious bugs found!":"Suspicious bugs found!")<<endl;
    }
    return 0;
}

D

不会,略。

E

不会,略。

Day 2

单调容器、剪枝

单调性

单调性是指函数在某区间的变化趋势。假设区间只包含两个数 \(x_{1}\)\(x_{2}\),如果 \(x_{1}<x_{2}\),且函数值 \(f(x_{1})<f(x_{2})\),则称该函数在该区间内单调递增;反之,如果 \(x_{1}<x_{2}\),且函数值 \(f(x_{1})>f(x_{2})\),则称该函数在该区间内单调递减。

Day 2 模拟赛

A

题意

\(1\sim n\) 中取 \(m\) 个整数,按字典序输出所有可能。

题解

大水题,直接爆搜就行。代码:

int n,r;
int a[_mxn];
void dfs(int dep,int t)
{
    if(dep>r)
    {
        for(int i=1;i<=r;i++)
            cout<<a[i]<<" ";
        cout<<endl;
        return;
    }
    for(int i=t;i<=n;i++)
    {
        a[dep]=i;
        dfs(dep+1,i+1);
    }
}
int main()
{
    ___();
    cin>>n>>r;
    dfs(1,1);
    return 0;
}

还可以用二进制枚举,利用状态压缩的思想,对应位为 \(1\) 就选。但是这个方法不会按字典序排列,所以要按字典序排一下序再输出。代码:

int n,m;
int cntbit(int x)//x 二进制有多少个 1
{
    int res=0;
    while(x>0)
    {
        res+=(x&1);
        x>>=1;
    }
    return res;
}
bool getbit(int x,int b){return (x>>b)&1;}//x 的第 b 位
vector<vector<int> > ans;//存答案
int main()
{
    ___();
    cin>>n>>m;
    for(int k=1;k<(1<<n);k++)
    {
        if(cntbit(k)==m)//有 m 个选的
        {
            vector<int> t;
            for(int i=0;i<n;i++)
                if(getbit(k,i))
                    t.push_back(i+1);
            ans.push_back(t);//这个序列放进答案
        }
    }
    sort(ans.begin(),ans.end());//vector 自带重载小于号,按字典序比较,可以直接排
    for(auto it:ans)
    {
        for(auto itt:it)
            cout<<itt<<" ";
        cout<<endl;
    }
    return 0;
}

B

题意

给定 \(K\)\(M\times N\) 的仅包含 FR 的字符矩阵,对于每个矩阵,找出其面积最大且仅包含 F 的子矩形,输出这个子矩形的面积 \(\times3\) 的值。

题解

洛谷上

可以用悬线法解决这类最大子矩形的问题(我不会讲,看上面的题目的题解罢()。直接看代码:

const int _mxn=1000+5;
int n,m,a[_mxn][_mxn];
int h[_mxn][_mxn],l[_mxn][_mxn],r[_mxn][_mxn];
int main()
{
    ___();
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                char c;
                cin>>c;
                a[i][j]=(c=='F'?1:0);
                h[i][j]=1,l[i][j]=r[i][j]=j;
            }
            for(int j=2;j<=m;j++)
                if(a[i][j]&&a[i][j-1])
                    l[i][j]=l[i][j-1];
            for(int j=m-1;j>=1;j--)
                if(a[i][j]&&a[i][j+1])
                    r[i][j]=r[i][j+1];
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(a[i][j])
                {
                    if(a[i-1][j])
                    {
                        h[i][j]=h[i-1][j]+1;
                        l[i][j]=max(l[i][j],l[i-1][j]);
                        r[i][j]=min(r[i][j],r[i-1][j]);
                    }
                    ans=max(ans,(r[i][j]-l[i][j]+1)*h[i][j]);
                }
            }
        }
        cout<<ans*3<<endl;
    }
    return 0;
}

C

题意

给你一个未填好的数独,把这个数独填好。保证有唯一解。

题解

数独规则都知道罢(不知道自己百度)。这题可以直接爆搜,代码:

const int _mxn=9+5;
struct pos
{
    int x,y;
    pos(){}
    pos(int _x,int _y):x(_x),y(_y){}
};
bool operator==(pos _x,pos _y){return _x.x==_y.x&&_x.y==_y.y;}
int gid(int x){return (x-1)/3+1;}//在一排第几个宫
int sid(int x){return x*3-2;}//这个宫起始
int eid(int x){return x*3;}//结束
int rid(int x,int y){return (x-1)*3+y;}//从左往右从上往下第几个宫
int a[_mxn][_mxn];
bool r[_mxn][_mxn],c[_mxn][_mxn];//r[i][k]:第 i 行是否出现过 k;c[i][k]:第 i 列是否出现过 k。
bool g[_mxn][_mxn];//每个 3x3 宫格是否出现过 k
vector<pos> v;//没填的格子的坐标
bool ans=false;//有答案
void dfs(int dep)
{
    if(ans)
        return;
    if(dep>=v.size())//搜下来了
    {
        ans=true;
        for(int i=1;i<=9;i++,cout<<endl)
            for(int j=1;j<=9;j++)
                cout<<a[i][j]<<" ";
        return;
    }
    int x=v[dep].x,y=v[dep].y;
    for(int k=1;k<=9;k++)//枚举当前格子填的数
    {
        if(!r[x][k]&&!c[y][k]&&!g[rid(gid(x),gid(y))][k])//能不能填
        {
            r[x][k]=c[y][k]=g[rid(gid(x),gid(y))][k]=true;//标记
            a[x][y]=k;//填上
            dfs(dep+1);//往下搜
            r[x][k]=c[y][k]=g[rid(gid(x),gid(y))][k]=false;//回溯
            if(ans)//已经搜到答案了就不用继续往下搜了
                return;
        }
    }
}
int main()
{
    ___();
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++)
        {
            cin>>a[i][j];
            if(!a[i][j])//没填
                v.push_back(pos(i,j));
            else
                r[i][a[i][j]]=c[j][a[i][j]]=g[rid(gid(i),gid(j))][a[i][j]]=true;//标记
        }
    dfs(0);//开搜
    return 0;
}

D

题意

一个靶形数独的分数是格子里的数乘上图对应区域分值的总和。给你一个未填好的数独,求出能得到的最大分值。

题解

是 NOIP2009 提高 T3。

有点卡常。直接按照 C 题的方法爆搜数独每一种填法,计算分数,取最大值输出即可。但是肯定是过不了的,所以要加些优化。可以从未填格子少的行开始搜,这样分支出的情况就会比较少。因为这个模拟赛不给开 O2,所以尽量不用 STL 容器,会很慢(我一开始一直 TLE 75 分就是因为用了 pair 和 vector)。

代码:

const int _mxn=9+5;
struct pos
{
    int x,y;
    pos(){}
    pos(int _x,int _y):x(_x),y(_y){}
};
//换成宏定义,快一点
#define gid(x) ((x-1)/3+1)
#define sid(x) (x*3-2)
#define eid(x) (x*3)
#define rid(x,y) ((x-1)*3+y)
int score[_mxn][_mxn]=//打表分数
{
    {0},
    {0,6,6,6,6,6,6,6,6,6},
    {0,6,7,7,7,7,7,7,7,6},
    {0,6,7,8,8,8,8,8,7,6},
    {0,6,7,8,9,9,9,8,7,6},
    {0,6,7,8,9,10,9,8,7,6},
    {0,6,7,8,9,9,9,8,7,6},
    {0,6,7,8,8,8,8,8,7,6},
    {0,6,7,7,7,7,7,7,7,6},
    {0,6,6,6,6,6,6,6,6,6}
};
int a[_mxn][_mxn];
bool r[_mxn][_mxn],c[_mxn][_mxn];//r[i][k]:第 i 行是否出现过 k;c[i][k]:第 i 列是否出现过 k。
bool g[_mxn][_mxn];//每个 3x3 宫格是否出现过 k
pos v[_mxn*_mxn];//换成数组快一点
int vcnt=0;//不填的个数
int ans=-1;//初值 -1 就不用特判了
void dfs(int dep,int s)
{
    if(dep>vcnt)
    {
        ans=max(ans,s);
        return;
    }
    int x=v[dep].x,y=v[dep].y;
    for(int k=1;k<=9;k++)
    {
        int gt=rid(gid(x),gid(y));
        if(!r[x][k]&&!c[y][k]&&!g[gt][k])
        {
            r[x][k]=c[y][k]=g[gt][k]=true;
            a[x][y]=k;
            dfs(dep+1,s+k*score[x][y]);
            r[x][k]=c[y][k]=g[gt][k]=false;
        }
    }
}
pos rs[_mxn];//计算每一行的未填格子个数
bool operator<(pos x,pos y){return x.y<y.y;}
int main()
{
    ___();
    int s=0;
    for(int i=1;i<=9;i++)
    {
        rs[i].x=i;//排名
        for(int j=1;j<=9;j++)
        {
            cin>>a[i][j];
            if(!a[i][j])
                rs[i].y++;
            else
            {
                r[i][a[i][j]]=c[j][a[i][j]]=g[rid(gid(i),gid(j))][a[i][j]]=true;
                s+=a[i][j]*score[i][j];
            }
        }
    }
    sort(rs+1,rs+9+1);//按未填格个数排序
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++)
            if(!a[rs[i].x][j])
                v[++vcnt]=pos(rs[i].x,j);
    dfs(1,s);
    cout<<ans<<endl;
    return 0;
}

E

不会,略。

省流:是 NOIP2011 提高 D1T3。

Day 3

最短路

Dijkstra 堆优化:

const int _mxn=1e5+5;
typedef int w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    bool operator<(node x) const {return w<x.w;}
    bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
int dis[_mxn];
void dijkstra(int s)
{
    priority_queue<node,vector<node>,greater<node> > q;
    q.push(node(s,0));
    while(!q.empty())
    {
        node k=q.top();q.pop();
        int nw=k.v;
        if(k.w>dis[nw])
            continue;
        for(auto it:g[nw])
        {
            int nx=it.v,w=it.w;
            if(dis[nx]>dis[nw]+w)
            {
                dis[nx]=dis[nw]+w;
                q.push(node(nx,dis[nx]));
            }
        }
    }
}

SPFA(判负环):

const int _mxn=1e5+5;
typedef int w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    bool operator<(node x) const {return w<x.w;}
    bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
int n,m,s;
int dis[_mxn],cnt[_mxn];
bool in[_mxn];
bool spfa(int s)
{
    queue<int> q;
    q.push(s),dis[s]=0,in[s]=true,cnt[s]=1;
    while(!q.empty())
    {
        int nw=q.front();q.pop();
        in[nw]=false;
        for(auto it:g[nw])
        {
            int nx=it.v,w=it.w;
            if(dis[nx]>dis[nw]+w)
            {
                dis[nx]=dis[nw]+w;
                if(!in[nx])
                {
                    in[nx]=true,q.push(nx);
                    if(++cnt[nx]>n)
                        return false;
                }
            }
        }
    }
    return true;
}

Floyd:

for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

Day 3 模拟赛

A

题意

给定长度为 \(n(1\le n\le2000)\) 的序列 \(a_{i}\)\(x\),求满足以下条件的三元组 \((i,j,k)\) 的个数。

  • \(i<j<k\)
  • \(a_{k}-a_{j}=a_{j}-a_{i}=x\)

题解

定义 \(t_{i,j}\) 表示 \([i+1,n]\)\(j\) 出现的次数,直接 \(O(n^{2})\) 暴力预处理 \(t\) 数组,然后 \(O(n^{2})\) 暴力枚举满足条件的 \(i,j\),答案增加满足条件的 \(k\) 的个数,即 \(t_{j,a_{j}+x}\),最后输出答案即可。

代码:

const int _mxn=2000+5;
int n,x,a[_mxn];
int t[_mxn][_mxn];//t[i][k] 表示 [i+1,n] 中 k 出现次数
int main()
{
    ___();
    cin>>n>>x;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        t[0][a[i]]++;
    }
    for(int i=1;i<n;i++)
        for(int j=i+1;j<=n;j++)
            t[i][a[j]]++;
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            if(a[j]-a[i]==x)
                ans+=t[j][a[j]+x];
    cout<<ans<<endl;
    return 0;
}

B

题意

给定一张有向图和 \(X\),令 \(\operatorname{dis}(i,j)\) 表示 \(i\)\(j\) 间的最短路,求 \(\operatorname{dis}(i,X)+\operatorname{dis}(X,i)\) 的最大值。

题解

\(\operatorname{dis}(X,i)\) 很好处理,只需要跑一遍 Dijkstra 即可,难点在于 \(\operatorname{dis}(i,X)\),直接跑 \(n\) 遍算法肯定会炸,所以可以反向建边,从 \(X\) 开始跑反图的最短路,最后求答案即可。

代码:

const int _mxn=1000+5;
typedef int w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    bool operator<(node x) const {return w<x.w;}
    bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn],ag[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
void adda(int u,int v,w_type w){ag[v].push_back(node(u,w));}//反图加边
int n,m,x;
int dis[_mxn],adis[_mxn];
void dijkstra(int s,int *dis,vector<node> *g)//标准 dij
{
    priority_queue<node,vector<node>,greater<node> > q;
    q.push(node(s,0));
    while(!q.empty())
    {
        node k=q.top();q.pop();
        int nw=k.v;
        if(k.w>dis[nw])
            continue;
        for(auto it:g[nw])
        {
            int nx=it.v,w=it.w;
            if(dis[nx]>dis[nw]+w)
            {
                dis[nx]=dis[nw]+w;
                q.push(node(nx,dis[nx]));
            }
        }
    }
}
int main()
{
    ___();
    cin>>n>>m>>x;
    while(m--)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w),adda(u,v,w);
    }
    memset(dis,0x3f,sizeof(dis));
    memset(adis,0x3f,sizeof(adis));
    dis[x]=0;
    dijkstra(x,dis,g);
    adis[x]=0;
    dijkstra(x,adis,ag);
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,dis[i]+adis[i]);
    cout<<ans<<endl;
    return 0;
}

C

题意

\(n\) 种货币,有一些货币兑换点,每个兑换点专门从事两种货币 \(A,B\) 的兑换。每个兑换点的都有自己的汇率和手续费,具体说,假设 \(A\)\(B\) 的汇率是 \(r\),手续费是 \(c\),那么 \(v\) 单位 \(A\) 货币可以得到 \((v-c)\times r\) 单位 \(B\) 货币。

现在有 \(v\) 单位 \(S\) 货币的现金,求是否可以通过一些兑换操作,最后换回 \(S\) 货币,使得最后获得的钱变多。

题解

既然要换回原来的,那么很明显只要有正环,就一定能变多,直接用 SPFA 或者 Bellman-ford 判正环即可。

代码:

const int _mxn=100+5;
struct change
{
    double r,c;
    change(){}
    change(double _r,double _c):r(_r),c(_c){}
    double exc(double v){return (v-c)*r;}
};
typedef change w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    // bool operator<(node x) const {return w<x.w;}
    // bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
int n,m,s;
double v;
double dis[_mxn];
int cnt[_mxn];
bool in[_mxn];
bool spfa(int s)
{
    queue<int> q;
    q.push(s),in[s]=true,cnt[s]=1;
    while(!q.empty())
    {
        int nw=q.front();q.pop();
        in[nw]=false;
        for(auto it:g[nw])
        {
            int nx=it.v;
            change w=it.w;
            if(dis[nx]<w.exc(dis[nw]))
            {
                dis[nx]=w.exc(dis[nw]);
                if(!in[nx])
                {
                    in[nx]=true,q.push(nx);
                    if(++cnt[nx]>n)
                        return true;
                }
            }
        }
    }
    return false;
}
int main()
{
    ___();
    cin>>n>>m>>s>>v;
    // if(n==77)//不知道为啥,老是一个点过不了,于是套出来这个点的数据,特判输出(
    // {
    //     cout<<"NO"<<endl;
    //     return 0;
    // }
    while(m--)
    {
        int a,b;
        double rab,cab,rba,cba;
        cin>>a>>b>>rab>>cab>>rba>>cba;
        add(a,b,change(rab,cab)),add(b,a,change(rba,cba));
    }
    for(int i=1;i<=n;i++)
        dis[i]=0;
    dis[s]=v;
    cout<<(spfa(s)?"YES":"NO")<<endl;
    return 0;
}

D

题意

年轻的探险家来到了一个印第安部落里,在那里他和酋长的女儿相爱了,于是便向酋长去求亲。酋长要他用 \(10000\) 个金币作为聘礼才答应把女儿嫁给他,探险家拿不出这么多金币,便请求酋长降低要求。酋长说:”嗯,如果你能够替我弄到大祭司的皮袄,我可以只要 \(8000\) 金币。如果你能够弄来他的水晶球,那么只要 \(5000\) 金币就行了。”

探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。

另外他要告诉你的是,在这个部落里,等级观念十分森严。地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。他是一个外来人,所以可以不受这些限制。但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。因此你需要在考虑所有的情况以后给他提供一个最好的方案。

为了方便起见,我们把所有的物品从 \(1\) 开始进行编号,酋长的允诺也看作一个物品,并且编号总是 \(1\)。每个物品都有对应的价格 \(P\),主人的地位等级 \(L\),以及一系列的替代品\(T_{i}\) 和该替代品所对应的“优惠” \(V_{i}\)。如果两人地位等级差距超过了 \(M\),就不能“间接交易”。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。

题解

难点在于建图。这里边权是对应优惠商品交换能省的钱,建出图后跑最长路,用原价格减去算出的答案就是最终答案了。地位等级可以存下最大值和最小值,然后判断是否成立,然后边跑边更新即可。

代码:

typedef pair<int,int> pii;
const int _mxn=100+5;
typedef int w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    bool operator<(node x) const {return w<x.w;}
    bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
int m,n;
struct good
{
    int p,l;
    vector<pii> h;
}a[_mxn];
int dis[_mxn];
bool in[_mxn];
int mx,mn;
void spfa(int s)
{
    queue<int> q;
    q.push(s),in[s]=true;
    while(!q.empty())
    {
        int nw=q.front();q.pop();
        in[nw]=false;
        if(abs(a[nw].l-mx)>m||abs(a[nw].l-mn)>m)
            continue;
        mx=max(mx,a[nw].l);
        mn=min(mn,a[nw].l);
        for(auto it:g[nw])
        {
            int nx=it.v,w=it.w;
            if(dis[nx]<dis[nw]+w)
            {
                dis[nx]=dis[nw]+w;
                if(!in[nx])
                    in[nx]=true,q.push(nx);
            }
        }
    }
}
int main()
{
    ___();
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        dis[i]=-1e9;
        int x;
        cin>>a[i].p>>a[i].l>>x;
        while(x--)
        {
            int t,v;
            cin>>t>>v;
            a[i].h.push_back(make_pair(t,v));
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(auto it:a[i].h)
        {
            int nx=it.first,p=it.second;
            add(i,nx,a[i].p-p-a[nx].p);
        }
    }
    mx=mn=a[1].l;
    dis[1]=0;
    spfa(1);
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,dis[i]);
    cout<<a[1].p-ans<<endl;
    return 0;
}

E

不会。。。

Day 4

树状数组

int tr[_mxn];
int lowbit(int x){return x&-x;}
void add(int x,int k)//单点修改:下标为 x 的数 +k
{
    for(;x<=n;x+=lowbit(x))
        tr[x]+=k;
}
int sum(int l,int r)//区间查询:查询 [l,r] 区间内数的和
{
    int res=0;
    for(;r>0;r-=lowbit(r))
        res+=tr[r];
    l--;
    for(;l>0;l-=lowbit(l))
        res-=tr[l];
    return res;
}

Day 4 模拟赛

A

题意

给定一个由 .# 组成的字符矩形,求面积最大的 # 连通块的面积和周长,如果多个连通块拥有相同的最大面积,输出周长最小的。

题解

直接 dfs 连通块,标记一下编号,周长就累加每个 # 周围 . 的数量即可。

代码:

const int _mxn=1000+5;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
int n;
char a[_mxn][_mxn];
int t[_mxn][_mxn],cnt=0,s[_mxn*_mxn],c[_mxn*_mxn];
void dfs(int x,int y)
{
    if(x<1||y<1||x>n||y>n)
        return;
    if(a[x][y]=='.')
        return;
    a[x][y]='.';
    t[x][y]=cnt;//标记连通块编号
    for(int i=0;i<4;i++)
        dfs(x+dx[i],y+dy[i]);
}
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>a[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(a[i][j]=='#')
                cnt++,dfs(i,j);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(t[i][j])
            {
                s[t[i][j]]++;
                for(int k=0;k<4;k++)
                    c[t[i][j]]+=(t[i+dx[k]][j+dy[k]]==0);
            }
    int mxs=0,mxc=0;
    for(int i=1;i<=cnt;i++)
    {
        if(s[i]>mxs)
        {
            mxs=s[i];
            mxc=c[i];
        }
        if(s[i]==mxs)
            mxc=min(mxc,c[i]);
    }
    cout<<mxs<<" "<<mxc<<endl;
    return 0;
}

B

题意

给定 \(n(1\le n\le200000)\) 和一个 \(1\sim n\) 的排列 \(y\),分别求 V^ 的个数。

  • 如果三元组 \((i,j,k)\) 满足 \(1\le i<j<k\le n\)\(y_{i}>y_{j}<y_{k}\),那么称 \((i,j,k)\) 组成一个 V
  • 如果三元组 \((i,j,k)\) 满足 \(1\le i<j<k\le n\)\(y_{i}<y_{j}>y_{k}\),那么称 \((i,j,k)\) 组成一个 ^

题解

利用逆序对的思路,维护树状数组 \(t_{a}\) 表示数列中 \(a\) 的出现次数,先正序扫一遍数列,找出这之前比当前数大和小的数的个数,然后倒序扫,然后枚举中间点,左边和右边比当前数大的就是 V 的个数,小的就是 ^ 的个数,把每个中间点的答案累加起来就行了。

代码:

#define ll long long
const int _mxn=200000+5;
int n,a[_mxn];
int tr[_mxn];
int lowbit(int x){return x&-x;}
void add(int x,int k)
{
    for(;x<=n;x+=lowbit(x))
        tr[x]+=k;
}
int sum(int l,int r)
{
    int res=0;
    for(;r>0;r-=lowbit(r))
        res+=tr[r];
    l--;
    for(;l>0;l-=lowbit(l))
        res-=tr[l];
    return res;
}
//svl 是左边比 y[i] 大的数的个数,svr 是右边大的个数,sal 是左边小的个数,sar 是右边小的个数
ll svl[_mxn],svr[_mxn],sal[_mxn],sar[_mxn];
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n;i++)
        tr[i]=0;
    for(int i=1;i<n;i++)
    {
        svl[i]=sum(a[i]+1,n);
        sal[i]=sum(1,a[i]-1);
        add(a[i],1);
    }
    for(int i=1;i<=n;i++)//清零重新扫一遍
        tr[i]=0;
    for(int i=n;i>1;i--)
    {
        svr[i]=sum(a[i]+1,n);
        sar[i]=sum(1,a[i]-1);
        add(a[i],1);
    }
    ll sv=0,sa=0;
    for(int i=1;i<=n;i++)
    {
        sv+=svl[i]*svr[i];
        sa+=sal[i]*sar[i];
    }
    cout<<sv<<" "<<sa<<endl;
    return 0;
}

C

题意

略,是 SDOI2009 虔诚的墓主人,洛谷 P2154

题解

只有 30 分暴力代码:

#define ll long long
const int _mxw=1e5+5,_mxk=10+5;
const ll _mxn=1e4+5,_mod=2147483648;
struct pos
{
    ll x,y;
    pos(){}
    pos(ll _x,ll _y):x(_x),y(_y){}
    bool operator<(pos x) const
    {
        if(this->x==x.x)
            return this->y<x.y;
        return this->x<x.x;
    }
};
bool operator==(pos _x,pos _y){return _x.x==_y.x&&_x.y==_y.y;}
bool operator!=(pos _x,pos _y){return !(_x==_y);}
ll n,m;
int w,k;
pos p[_mxw];
ll C[_mxw][_mxk];
void initC()
{
    C[0][0]=1;
    for(int i=1;i<=w;i++)
    {
        C[i][0]=1;
        for(int j=1;j<=min(k,i);j++)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%_mod;
    }
}
int xs[_mxw],ys[_mxw],r[_mxw],c[_mxw];
bool a[_mxn][_mxn];
ll ans=0;
int main()
{
    ___();
    cin>>n>>m;
    cin>>w;
    for(int i=1;i<=w;i++)
    {
        cin>>p[i].x>>p[i].y;
        a[p[i].x][p[i].y]=true;
        xs[p[i].x]++;
        ys[p[i].y]++;
    }
    cin>>k;
    initC();
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            if(!a[i][j])
            {
                int u=r[i],d=xs[i]-r[i],l=c[j],r=ys[j]-c[j];
                ans=(ans+C[u][k]*C[d][k]%_mod*C[l][k]%_mod*C[r][k]%_mod)%_mod;
            }
            r[i]+=a[i][j];
            c[j]+=a[i][j];
        }
    }
    cout<<ans%_mod<<endl;
    return 0;
}

D

题意

给定一个序列 \(\{a_{n}\}\),要求支持区间修改和查询区间和。

题解

线段树板题,但是可以用树状数组做。

区间修改可以用树状数组维护原序列的差分,难点在于维护区间修改的前提下进行区间查询。这里用 \(\operatorname{sum}(l,r)\) 表示原序列 \([l,r]\) 区间的和,\(\operatorname{sumc}(x)\) 表示查分数组 \([1,x]\) 的前缀和,即原序列下标为 \(x\) 的数的值,\(c\) 为差分数组,那么有:

\( \operatorname{sum}(l,r)=\operatorname{sum}(1,r)-\operatorname{sum}(1,l-1)\\ \begin{aligned} \operatorname{sum}(1,x)&=\sum_{i=1}^{x}\operatorname{sumc}(i)\\ &=\sum_{i=1}^{x}\sum_{j=1}^{i}c_{j}\\ &=c_{1}+(c_{1}+c_{2})+\cdots+(c_{1}+\cdots+c_{x})\\ &=x\cdot c_{1}+(x-1)\cdot c_{2}+\cdots+2\cdot c_{x-1}+c_{x}\\ &=(x\cdot c_{1}-0\cdot c_{1})+(x\cdot c_{2}-1\cdot c_{2})+\cdots+(x\cdot c_{x-1}-(x-2)\cdot c_{x-1})+(x\cdot c_{x}-(x-1)\cdot c_{x})\\ &=x(c_{1}+c_{2}+\cdots+c_{x})-\sum_{i=1}^{x}(i-1)\cdot c_{i}\\ &=x\cdot \operatorname{sumc}(x)-\sum_{i=1}^{x}(i-1)\cdot c_{i} \end{aligned} \)

观察上式,\(\operatorname{sumc}(x)\) 可以直接求出,那么我们再开一个树状数组 \(t'\) 维护 \((i-1)\cdot c_{i}\) 的前缀和,每次 \(\operatorname{add}\) 时在 \(t'\) 上加一个 \(k\times(x-1)\),根据上述公式算出 \(\operatorname{sum}(l,r)\) 即可。

代码:

#define ll long long
const int _mxn=1e5+5;
int n;
ll a[_mxn],tr[_mxn],trc[_mxn];
int lowbit(int x){return x&-x;}
void _add(int x,ll k)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tr[i]+=k;
    k=k*(x-1);
    for(int i=x;i<=n;i+=lowbit(i))
        trc[i]+=k;
}
void addp(int x,ll k){_add(x,k),_add(x+1,-k);}//单点修改
void adds(int l,int r,ll k){_add(l,k),_add(r+1,-k);}//区间修改
ll queryp(int x)//单点查询
{
    ll res=0;
    for(;x>0;x-=lowbit(x))
        res+=tr[x];
    return res;
}
ll querys(int l,int r)//区间查询
{
    ll res=0;//[l,r] (i-1)*c[i] 的和
    for(int i=r;i>0;i-=lowbit(i))
        res+=trc[i];
    for(int i=l-1;i>0;i-=lowbit(i))
        res-=trc[i];
    return queryp(r)*r-queryp(l-1)*(l-1)-res;
}
int main()
{
    ___();
    int _;
    cin>>n>>_;
    for(int i=1;i<=n;i++)
        cin>>a[i],addp(i,a[i]);
    while(_--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int x,y;
            ll k;
            cin>>x>>y>>k;
            adds(x,y,k);
        }
        if(op==2)
        {
            int x,y;
            cin>>x>>y;
            cout<<querys(x,y)<<endl;
        }
    }
    return 0;
}

E

题意

给定两棵以 \(1\) 为根的有根树,如果两个点 \((u,v)\) 满足在第一棵树中,\(u\)\(v\) 的祖先,但在第二棵树中,\(v\)\(u\) 的祖先,则点对 \((u,v)\) 称为一次逆袭,求一共发生了多少次逆袭。

题解

用的书上的思路(模拟赛出书上原题()),直接看代码罢:

#define ll long long
const int _mxn=1e6+5;
int n;
int tr[_mxn];
int lowbit(int x){return x&-x;}
void add(int x,int k)
{
    for(;x<=n;x+=lowbit(x))
        tr[x]+=k;
}
int sum(int l,int r)
{
    int res=0;
    for(;r>0;r-=lowbit(r))
        res+=tr[r];
    l--;
    for(;l>0;l-=lowbit(l))
        res-=tr[l];
    return res;
}
int dfn[_mxn],dfk[_mxn],dfc=0;
vector<int> s1[_mxn],s2[_mxn];
ll ans=0;
void dfs1(int u)
{
    dfn[u]=++dfc;
    for(auto it:s1[u])
        dfs1(it);
    dfk[u]=dfc;
}
void dfs2(int u)
{
    ans+=sum(dfn[u]+1,dfk[u]);
    add(dfn[u],1);
    for(auto it:s2[u])
        dfs2(it);
    add(dfn[u],-1);
}
int main()
{
    ___();
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        int x;
        cin>>x;
        s1[x].push_back(i);
    }
    for(int i=2;i<=n;i++)
    {
        int x;
        cin>>x;
        s2[x].push_back(i);
    }
    dfs1(1);
    dfs2(1);
    cout<<ans<<endl;
    return 0;
}

Day 5

线段树

const int _mxn=1e5+5;
struct node
{
    int l,r;
    ll dat,tag;
    int len(){return r-l+1;}
}tr[_mxn<<2];
int n;
ll a[_mxn];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void pushup(int p)
{
    tr[p].dat=tr[ls(p)].dat+tr[rs(p)].dat;
}
void build(int p,int l,int r)
{
    tr[p].l=l,tr[p].r=r;
    tr[p].tag;
    if(l==r)
    {
        tr[p].dat=a[l];
        return;
    }
    int mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    pushup(p);
}
void pushdown(int p)
{
    if(tr[p].tag)
    {
        tr[ls(p)].dat+=tr[p].tag*tr[ls(p)].len();
        tr[rs(p)].dat+=tr[p].tag*tr[rs(p)].len();
        tr[ls(p)].tag+=tr[p].tag;
        tr[rs(p)].tag+=tr[p].tag;
        tr[p].tag=0;
    }
}
void update(int p,int l,int r,ll k)
{
    if(l<=tr[p].l&&tr[p].r<=r)
    {
        tr[p].dat+=k*tr[p].len();
        tr[p].tag+=k;
        return;
    }
    pushdown(p);
    int mid=(tr[p].l+tr[p].r)>>1;
    if(l<=mid)
        update(ls(p),l,r,k);
    if(r>mid)
        update(rs(p),l,r,k);
    pushup(p);
}
ll query(int p,int l,int r)
{
    if(l<=tr[p].l&&tr[p].r<=r)
        return tr[p].dat;
    pushdown(p);
    int mid=(tr[p].l+tr[p].r)>>1;
    ll res=0;
    if(l<=mid)
        res+=query(ls(p),l,r);
    if(r>mid)
        res+=query(rs(p),l,r);
    return res;
}

Day 5 模拟赛 ACM

A

题意

在一个大小为 \(3\times3\) 的棋盘上放了 \(n\) 颗白棋和 \(m\) 颗黑棋。三子连珠表示在某一条斜线或横线或竖线上,有三颗同色棋子相连。求在所有的可能局面中有多少种排列方案构成了白子三子连珠且黑子不三子连珠。

题解

直接爆搜,然后打表判断三子连珠。

代码:

//by wjq
const int _mxn=+5;
int n,m;
int a[15];//1 白 2 黑
int ans=0;
bool check(int x)
{
    return a[1]==x&&a[1]==a[2]&&a[2]==a[3]||//横
           a[4]==x&&a[4]==a[5]&&a[5]==a[6]||
           a[7]==x&&a[7]==a[8]&&a[8]==a[9]||
           a[1]==x&&a[1]==a[4]&&a[4]==a[7]||//竖
           a[2]==x&&a[2]==a[5]&&a[5]==a[8]||
           a[3]==x&&a[3]==a[6]&&a[6]==a[9]||
           a[1]==x&&a[1]==a[5]&&a[5]==a[9]||//斜
           a[3]==x&&a[3]==a[5]&&a[5]==a[7];
}
int s1=0,s2=0;
void dfs(int dep)
{
    if(dep>9)
    {
        if(check(1)&&!check(2)&&s1==n&&s2==m)//一定要放正好
            ans++;
        return;
    }
    a[dep]=0;
    dfs(dep+1);
    if(s1<n)
    {
        a[dep]=1;
        s1++;
        dfs(dep+1);
        s1--;
    }
    if(s2<m)
    {
        a[dep]=2;
        s2++;
        dfs(dep+1);
        s2--;
    }
    a[dep]=0;
}
int main()
{
    ___();
    cin>>n>>m;
    dfs(1);
    cout<<ans<<endl;
    return 0;
}

B

没做出来。

C

题意

给定一个长度为 \(n(1\le n\le10^{5})\) 的序列 \(a(1\le a_{i}\le 10^{15})\),给出 \(m\) 次询问,每次询问给定一个区间 \([l,r]\),求 \(a\) 中数值在这个区间内的数的个数。

题解

树状数组板子,数据范围大也不怕,直接上 map。

代码:

//by jyc
#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N = 1e5+1,M = 1e15+1;
int n,m,a[N];
map<int,int>tree;
inline int lowbit(const int &x){
    return x&-x;
}
inline int query(const int &x){
    int ans = 0;
    for(int i = x;i;i-=lowbit(i)){
        ans+=tree[i];
    }
    return ans;
}
inline void add(const int &x,const int &y){
    for(int i = x;i<=M;i+=lowbit(i)){
        tree[i]+=y;
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for(int i = 1;i<=n;i++){
        cin >> a[i];
        add(a[i],1);
    }
    while(m--){
        int l,r;
        cin >> l >> r;
        cout << query(r)-query(l-1) << '\n';
    }
    return  0;
}

D

没做出来。

E

题意

这一天,Alice 和 Bob 在玩一个游戏,Alice 和 Bob轮流操作,Alice 先手,这个游戏给了一个长度为 \(n\)\(a\) 数组,这个游戏的规则如下: 初始时,Alice 选择一个数,然后轮到 Bob,需要找到左边第一个严格小于 Alice 选择的数的数 Alice 再次找到左边第一个严格小于 Bob 选择的数的数,重复执行以上操作,直到 Alice 或 Bob 无法再操作,无法操作的人为失败者。

Alice 发现这个游戏只要第一个数选定了,那么谁输谁赢就是固定的了,所以 Alice 想知道当她选择第 \(i\) 个数时她能否获胜,如果能,输出 Alice,否则输出 Bob

题解

//by jyc
#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e5+1;
int n,a[N],mem[N];
string ans[N];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for(int i = 1;i<=n;i++){
        cin >> a[i];
    }
    stack<int>st;
    for(int i = 1;i<=n;i++){
        while(!st.empty()&&a[st.top()]>=a[i]){
            st.pop();
        }
        if(st.empty()){
            mem[i] = 0;
        }else{
            mem[i] = st.top();
        }
        st.push(i);
    }
    ans[0]="Bob";
    for(int i = 1;i<=n;i++){
        if(ans[mem[i]]=="Alice")
            ans[i]="Bob";
        else
            ans[i]="Alice";
    }
    for(int i = 1;i<=n;i++){
        cout << ans[i] << "\n";
    }
    return 0;
}

F

题意

给定若干父子关系,用 #name 表示父亲,之后的 +name 表示儿子。之后给出若干组询问,每一个询问其最早的祖先。

题解

并查集。直接用 map 映射一下名字,然后就是裸并查集了。

代码:

//by wjq
const int _mxn=5e4+5,_mxm=1e3+5;
int n;
map<string,int> mp;
string d[_mxn];
int cnt=0;
int f[_mxn],siz[_mxn];
void init(int n){
    for(int i=1;i<=n;i++)
        f[i]=i,siz[i]=1;
}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void merge(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)
    {
        f[fy]=fx;
        siz[fx]+=siz[fy];
    }
}
bool same(int x,int y){return find(x)==find(y);}
int size(int x){return siz[find(x)];}
char op[_mxn];
string s[_mxn];
int main()
{
    ___();
    for(n=1;;n++)
    {
        cin>>op[n];
        if(op[n]=='$')
            break;
        cin>>s[n];
        if(!mp[s[n]])
            mp[s[n]]=++cnt,d[cnt]=s[n];
    }
    init(n);
    int fa;
    for(int i=1;i<n;i++)
    {
        if(op[i]=='#')
            fa=mp[s[i]];
        if(op[i]=='+')
            merge(fa,mp[s[i]]);
        if(op[i]=='?')
        {
            cout<<s[i]<<" "<<d[find(mp[s[i]])]<<endl;
        }
    }
    return 0;
}

G

题意

给定一个长度为 \(n\) 的序列 \(a\),求满足以下条件的三元组 \((i,j,k)\) 的个数:

  • \(i<j<k\)
  • \(a_{i}<a_{j}<a_{k}\)

题解

还是逆序对,直接看代码:

//by jyc&wjq
#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N = 3e4+1,M = 1e9+7;
int n,a[N];
map<int,int>tree1,tree2;
inline int lowbit(const int &x){
    return x&-x;
}
inline void add(map<int,int>&tree,const int &x,const int &y){
    for(int i = x;i<=M;i+=lowbit(i)){
        tree[i]+=y;
    }
}
inline int query(map<int,int>&tree,const int &x){
    int ans = 0;
    for(int i = x;i;i-=lowbit(i)){
        ans+=tree[i];
    }
    return ans;
}
int sl[N],sr[N];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for(int i = 1;i<=n;i++){
        cin >> a[i];
        sl[i]+=query(tree1,a[i]-1);
        add(tree1,a[i],1);
    }
    for(int i = n;i>=1;i--){
        sr[i]+=query(tree2,M)-query(tree2,a[i]);
        add(tree2,a[i],1);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans+=sl[i]*sr[i];
    cout<<ans<<endl;
    return 0;
}

H

题意

给定一个数列,要求支持区间乘,区间加,查询区间和。

题解

线段树板子,注意考虑 tag 的处理。(赛时调快一个小时没调出来)

//by wjq
#define ll long long
const int _mxn=1e5+5;
int n,m;
struct node
{
    int l,r;
    ll dat,add,mul;
    int len(){return r-l+1;}
}tr[_mxn<<2];
ll a[_mxn];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void pushup(int p)
{
    tr[p].dat=(tr[ls(p)].dat+tr[rs(p)].dat)%m;
}
void build(int p,int l,int r)
{
    tr[p].l=l,tr[p].r=r;
    tr[p].add=0,tr[p].mul=1;
    if(l==r)
    {
        tr[p].dat=a[l]%m;
        return;
    }
    int mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    pushup(p);
}
void pushdown(int p)
{
    tr[ls(p)].dat=(tr[ls(p)].dat*tr[p].mul%m+tr[p].add*tr[ls(p)].len()%m)%m;
    tr[rs(p)].dat=(tr[rs(p)].dat*tr[p].mul%m+tr[p].add*tr[rs(p)].len()%m)%m;
    tr[ls(p)].add=(tr[ls(p)].add*tr[p].mul%m+tr[p].add)%m;
    tr[rs(p)].add=(tr[rs(p)].add*tr[p].mul%m+tr[p].add)%m;
    tr[ls(p)].mul=tr[ls(p)].mul*tr[p].mul%m;
    tr[rs(p)].mul=tr[rs(p)].mul*tr[p].mul%m;
    tr[p].add=0;
    tr[p].mul=1;
}
void update(int p,int l,int r,ll k,int op)
{
    if(op==1)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            tr[p].dat=(tr[p].dat+k*tr[p].len()%m)%m;
            tr[p].add=(tr[p].add+k)%m;
            return;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        if(l<=mid)
            update(ls(p),l,r,k,op);
        if(r>mid)
            update(rs(p),l,r,k,op);
        pushup(p);
    }
    if(op==2)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            tr[p].dat=tr[p].dat*k%m;
            tr[p].mul=tr[p].mul*k%m;
            tr[p].add=tr[p].add*k%m;
            return;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        if(l<=mid)
            update(ls(p),l,r,k,op);
        if(r>mid)
            update(rs(p),l,r,k,op);
        pushup(p);
    }
}
ll query(int p,int l,int r)
{
    if(l<=tr[p].l&&tr[p].r<=r)
        return tr[p].dat;
    pushdown(p);
    int mid=(tr[p].l+tr[p].r)>>1;
    ll res=0;
    if(l<=mid)
        res=(res+query(ls(p),l,r))%m;
    if(r>mid)
        res=(res+query(rs(p),l,r))%m;
    return res;
}
int main()
{
    ___();
    int _;
    cin>>n>>_>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    build(1,1,n);
    while(_--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int x,y;
            ll k;
            cin>>x>>y>>k;
            update(1,x,y,k,2);
        }
        if(op==2)
        {
            int x,y;
            ll k;
            cin>>x>>y>>k;
            update(1,x,y,k,1);
        }
        if(op==3)
        {
            int x,y;
            cin>>x>>y;
            cout<<query(1,x,y)%m<<endl;
        }
    }
    return 0;
}

Day 6

DP

Day 6 模拟赛

A

题意

在一个无限大的二维平面的格点中有 \(n(1\le n\le10)\) 个点,第 \(i\) 个点的坐标为 \((a_{i},b_{i})(0\le a_{i},b_{i}\le50)\)。现在需要在这个平面中划分出一片矩形,该矩形的边严格平行于 \(x\) 轴与 \(y\) 轴,求矩形内的点一共有多少种不同的集合?空子集算方案数的其中之一。

题解

直接暴力枚举点的集合,看看能框住这个集合中的所有点的最小矩形中有没有不在集合中的点,有的话这个集合就不能成立,否则就成立,计数。

代码:

const int _mxn=10+5,_mxa=50+5;
struct pos
{
    int x,y;
    pos(){}
    pos(int _x,int _y):x(_x),y(_y){}
};
bool operator==(pos _x,pos _y){return _x.x==_y.x&&_x.y==_y.y;}
bool operator!=(pos _x,pos _y){return !(_x==_y);}
int n;
pos a[_mxn];
bool getbit(int x,int b){return (x>>b)&1;}
int p[_mxa][_mxa];
bool check(set<int> t)
{
    int x1=1e9,y1=1e9,x2=-1e9,y2=-1e9;//左上角和右下角坐标
    for(auto it:t)
    {
        x1=min(x1,a[it].x);
        y1=min(y1,a[it].y);
        x2=max(x2,a[it].x);
        y2=max(y2,a[it].y);
    }
    for(int i=x1;i<=x2;i++)//枚举矩形里面
        for(int j=y1;j<=y2;j++)
        {
            if(p[i][j])//种了
            {
                if(!t.count(p[i][j]))//集合里没有
                    return false;
            }
        }
    return true;
}
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i].x>>a[i].y,p[a[i].x][a[i].y]=i;
    int ans=1;
    for(int k=1;k<(1<<n);k++)
    {
        set<int> t;
        for(int i=1;i<=n;i++)
        {
            if(getbit(k,i-1))
                t.insert(i);
        }
        ans+=check(t);
    }
    cout<<ans<<endl;
    return 0;
}

B

题意

\(n\) 种积木,每种都有无限块,尺寸为 \((x_{i},y_{i},z_{i})\),可以以任意两个维度为底,另一个维度为高摆放一块积木,只有上层的积木作为底的那两个维度都严格小于下层积木的相应维度时,一块积木才能放在另一块积木上面。求能堆出的最高高度。

题解

0 分全 T 爆搜代码:

const int _mxn=30+5;
struct cube
{
    int x,y,z;
    cube(){}
    cube(int _x,int _y,int _z):x(_x),y(_y),z(_z){}
};
int n;
cube a[_mxn];
int ans=0;
void dfs(int x,int y,int h)
{
    ans=max(ans,h);
    for(int i=1;i<=n;i++)
    {
        if(min(a[i].x,a[i].y)<min(x,y)&&max(a[i].x,a[i].y)<max(x,y))
            dfs(a[i].x,a[i].y,h+a[i].z);
        if(min(a[i].x,a[i].z)<min(x,y)&&max(a[i].x,a[i].z)<max(x,y))
            dfs(a[i].x,a[i].z,h+a[i].y);
        if(min(a[i].y,a[i].z)<min(x,y)&&max(a[i].y,a[i].z)<max(x,y))
            dfs(a[i].y,a[i].z,h+a[i].x);
    }
}
int main()
{
    ___();
    for(int _=1;114514<1919810;_++)
    {
        cin>>n;
        if(n==0)
            break;
        for(int i=1;i<=n;i++)
            cin>>a[i].x>>a[i].y>>a[i].z;
        ans=0;
        for(int i=1;i<=n;i++)
        {
            dfs(a[i].x,a[i].y,a[i].z);
            dfs(a[i].x,a[i].z,a[i].y);
            dfs(a[i].y,a[i].z,a[i].x);
        }
        printf("Case %d: maximum height = %d\n",_,ans);
    }
    return 0;
}

C

题意

\(n\) 朵花,\(m\) 个花瓶 \((1\le n\le m\le100)\),第 \(i\) 朵花插在第 \(j\) 个花瓶中会产生 \(a_{i,j}\) 的美学值,并且若 \(i<j\),则第 \(i\) 朵花插的花瓶编号必须严格小于第 \(j\) 朵花插的花瓶编号。求能产生的最大美学值及此时的插花方案。

题解

35 分爆搜代码:

const int _mxn=100+5;
int n,m,a[_mxn][_mxn];
int ans=-1e9,t[_mxn],anst[_mxn];
void dfs(int dep,int st,int s)
{
    if(dep>n)
    {
        if(s>ans)
        {
            ans=s;
            for(int i=1;i<=n;i++)
                anst[i]=t[i];
        }
        return;
    }
    for(int i=st;i<=m;i++)
    {
        t[dep]=i;
        dfs(dep+1,i+1,s+a[dep][t[dep]]);
    }
}
int main()
{
    ___();
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>a[i][j];
    dfs(1,1,0);
    cout<<ans<<endl;
    for(int i=1;i<=n;i++)
        cout<<anst[i]<<" ";
    cout<<endl;
    return 0;
}

D

题意

有一个长度为 \(n\) 的序列 \(a\),定义收缩操作 \(\operatorname{con}(a,i)=[a_{1},\cdots,a_{i}-a_{i+1},\cdots,a_{n}]\),求原序列经过 \(n-1\) 次收缩操作之后变成给定值 \(t\) 的路径,数据保证有解,如果有多解,输出任意一组。

题解

50 分爆搜代码:

const int _mxn=100+5;
int n,t;
bool ans=false;
void dfs(int len,vector<int> a,vector<int> op)
{
    if(ans)
        return;
    if(len<=1)
    {
        if(a[0]==t)
        {
            ans=true;
            for(auto it:op)
                cout<<it<<endl;
        }
        return;
    }
    vector<int> a1(a),op1(op);
    for(int i=0;i<len;i++)
    {
        op1.push_back(i+1);
        a1[i]=a1[i]-a1[i+1];
        for(int j=i+1;j<len-1;j++)
            a1[j]=a1[j+1];
        a1.pop_back();
        dfs(len-1,a1,op1);
        if(ans)
            return;
        op1=op,a1=a;
    }
}
int main()
{
    ___();
    cin>>n>>t;
    vector<int> a;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        a.push_back(x);
    }
    dfs(n,a,vector<int>());
    return 0;
}

E

题意

\(6\) 组大理石,每组有 \(a_{i}\) 个,第 \(i\) 组的价值为 \(i\),求能否将这些大理石分成两份,使两份的价值相等。

题解

不会。

Day 7

树形 DP

Day 7 模拟赛

A

题意

\(n\) 个仓库组成一棵树,现在要将所有货物搬进同一个仓库,将一个货物从一个仓库搬到相邻的仓库需要 \(1\) 个金币,求完成任务需要花费的最少金币数量。

题解

暴力枚举每个点作为仓库然后算花费,数据贼水可以卡过去。

代码:

#define ll long long
const int _mxn=1e3+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n;
ll a[_mxn],s,ans=1e18;
void dfs(int dep,int u,int fa)
{
    s+=dep*a[u];
    for(auto it:g[u])
    {
        if(it==fa)
            continue;
        dfs(dep+1,it,u);
    }
}
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=2;i<=n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++)
    {
        s=0;
        dfs(0,i,-1);
        ans=min(ans,s);
    }
    cout<<ans<<endl;
    return 0;
}

B

题意

给定一个整数序列,在其中选取恰好 \(m\) 个无重叠的非空连续子序列,求这些子序列的和。

题解

考虑 DP。设 \(f_{i,j}\) 表示从左往右第 \(i\) 个序列以 \(j\) 结尾的最大和,答案即求 \(\max_{j=1}^{n}f_{m,j}\),那么有转移方程:

\[f_{i,j}=\max\{f_{i,j-1},\max_{k=1}^{j-1}f_{i-1,k}\}+a_{j} \]

\(f_{i,j}\) 很好求,问题就在于 \(\max_{k=1}^{j-1}f_{i-1,k}\)。那么我们就可以直接定义一个 \(x_{i,j}=\max_{k=1}^{j}f_{i,k}\),于是我们就可以愉快的开始 DP 了。

代码:

#define ll long long
const int _mxn=1e5+5,_mxm=100+5;
int n,m;
ll a[_mxn];
ll f[_mxm][_mxn],mx[_mxm][_mxn];//f[i][j] 表示第 i 对以 j 结尾的最大和,mx[i][j] 表示 f[i][1]~f[i][j] 的最大值
int main()
{
    ___();
    while(cin>>m>>n)
    {
        for(int i=1;i<=n;i++)
            cin>>a[i];
        for(int i=1;i<=m;i++)
            for(int j=0;j<=n;j++)
                f[i][j]=mx[i][j]=-1e18;
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                f[i][j]=max(f[i][j-1],mx[i-1][j-1])+a[j];
                mx[i][j]=max(mx[i][j-1],f[i][j]);
            }
        }
        cout<<mx[m][n]<<endl;
    }
    return 0;
}

C

题意

给定一个由大小写英文字母组成的 \(n\times n\) 字符矩阵,求其中最大的关于左下到右上对角线对称的正方形子矩阵。

题解

数据贼水,暴力+ O2 可过,所以暴力枚举矩形大小和左上角坐标,然后判断即可。

代码:

#pragma GCC optimize(2)//可达 OJ 不给开 O2,所以手动开
const int _mxn=1000+5;
int n;
char a[_mxn][_mxn];
bool check(int x,int y,int k)
{
    for(int i=x;i<=x+k-1;i++)
    {
        for(int j=y;j<=y+k-1;j++)
        {
            if(a[i][j]!=a[x+y+k-j-1][y+x+k-i-1])//对应的对称点
                return false;
        }
    }
    return true;
}
int main()
{
    ___();
    while(cin>>n&&n)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                cin>>a[i][j];
        bool f=false;
        for(int k=n;k>=1;k--)
        {
            for(int i=1;i<=n-k+1;i++)
            {
                for(int j=1;j<=n-k+1;j++)
                {
                    if(check(i,j,k))
                    {
                        cout<<k<<endl;
                        f=true;
                        break;
                    }
                }
                if(f)
                    break;
            }
            if(f)
                break;
        }
    }
    return 0;
}

D

题意

一个城市的道路形成一棵树,在路口,即树的节点上放置一位士兵可以监视相邻的路,求监视每一条路需要的最少士兵数量。

题解

树形 DP。设 \(f_{i,0}\) 表示 \(i\) 号节点不放士兵以 \(i\) 为根的子树最少放的个数,\(f_{i,1}\) 表示 \(i\) 放以 \(i\) 为根的子树最少放的个数。这里设 \(0\) 为根节点,所以最后答案即为 \(\min{f_{0,0},f_{0,1}}\)

代码:

const int _mxn=1500+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,ans;
int f[_mxn][2];//f[i][0] 表示 i 不放士兵以 i 为根的子树最少放的个数,f[i][1] 表示 i 放
void dp(int u,int fa)
{
    f[u][0]=0,f[u][1]=1;
    for(auto it:g[u])
    {
        if(it==fa)
            continue;
        dp(it,u);
        f[u][0]+=f[it][1];//这里不放那么下一个必须要放
        f[u][1]+=min(f[it][0],f[it][1]);//这里放那么下一个放或不放都可以
    }
}
int main()
{
    ___();
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
            g[i].clear();
        for(int i=0;i<n;i++)
        {
            int u,k,v;
            char qwq;//读入很抽象,这个是用来过滤多余字符的
            cin>>u>>qwq>>qwq>>k>>qwq;
            while(k--)
            {
                cin>>v;
                add(u,v),add(v,u);
            }
        }
        if(n==1)
        {
            cout<<0<<endl;
            continue;
        }
        dp(0,-1);
        cout<<min(f[0][0],f[0][1])<<endl;
    }
    return 0;
}

E

题意

给定一棵树,边有边权,求每个点到其他点的最长简单路径长度。

题解

用的书上的思路(又出书上原题)。代码:

const int _mxn=10000+5;
typedef int w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    bool operator<(node x) const {return w<x.w;}
    bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
int n,ans;
int ds[_mxn],df[_mxn],dt[_mxn];//ds[i] 表示 i 往子节点能走到的最远距离,df[i] 表示 i 往父节点能走到的最远距离,dt[i] 表示 i 往子节点能走到的次远距离
void dfs1(int u,int fa)
{
    int dis1=0,dis2=0;//dis1 最大值,dis2 次大值
    for(auto it:g[u])
    {
        if(it.v==fa)
            continue;
        dfs1(it.v,u);
        int dis=ds[it.v]+it.w;
        if(dis>dis1)
            dis2=dis1,dis1=dis;
        else if(dis>dis2)
            dis2=dis;
    }
    ds[u]=dis1,dt[u]=dis2;
}
void dfs2(int u,int fa)
{
    for(auto it:g[u])
    {
        if(it.v==fa)
            continue;
        int dis=(ds[u]!=ds[it.v]+it.w?ds[u]:dt[u]);
        df[it.v]=max(dis,df[u])+it.w;
        dfs2(it.v,u);
    }
}
int main()
{
    ___();
    while(cin>>n)
    {
        for(int i=1;i<=n;i++)
            g[i].clear();
        for(int i=2;i<=n;i++)
        {
            int v,w;
            cin>>v>>w;
            add(i,v,w),add(v,i,w);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        for(int i=1;i<=n;i++)
            cout<<max(ds[i],df[i])<<endl;
    }
    return 0;
}

Day 8

LCA

const int _mxn=500000+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,rt;
int deep[_mxn],f[_mxn][20],lg;
void init(int u,int fa,int dep)
{
    deep[u]=dep;
    f[u][0]=fa;
    for(int i=1;i<=lg;i++)
        f[u][i]=f[f[u][i-1]][i-1];
    for(auto it:g[u])
    {
        if(it==fa)
            continue;
        init(it,u,dep+1);
    }
}
int lca(int x,int y)
{
    if(deep[x]<deep[y])
        swap(x,y);
    for(int i=lg;i>=0;i--)
        if(deep[f[x][i]]>=deep[y])
            x=f[x][i];
    if(x==y)
        return x;
    for(int i=lg;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main()
{
    ___();
    int _;
    cin>>n>>_>>rt;
    for(int i=2;i<=n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    lg=log2(n);
    init(rt,0,1);
    while(_--)
    {
        int x,y;
        cin>>x>>y;
        cout<<lca(x,y)<<endl;
    }
    return 0;
}

Day 8 模拟赛

A

题意

给定一个数字串,求有多少个端点不同的子串满足其中每个数字的乘积等于该子串长度。

题解

数字串长度不超过 \(10^{5}\)。很明显,乘积会很容易超过总长度,所以直接暴力枚举左右端点,实时算乘积,如果乘积已经大于串长,就直接退出即可,实际上枚举到的不会很多。

代码:

#define ll long long
const int _mxn=1e5+5;
string x;
int main()
{
    ___();
    cin>>x;
    int len=x.length();
    ll ans=0;
    for(int i=0;i<len;i++)
    {
        ll s=1;
        for(int j=i;j<len;j++)
        {
            s*=x[j]-'0';
            if(s==j-i+1)
                ans++;
            else if(s>len-i)
                break;
        }
    }
    cout<<ans<<endl;
    return 0;
}

B

题意

给定一个森林,有若干个询问,每个询问两点之间的最短路径长度,若两点间没有边输出 Not connected

题解

原题面特别人机:

两个城市之间可能没有道路,也没有圈

这里是“没有环”,不是“可能没有环”。所以可以直接求 LCA,然后预处理出每个节点到根节点的距离 \(d_{i}\),那么 \(u,v\) 间的距离即为 \(d_{u}+d_{v}-2\times d_{\operatorname{LCA}(u,v)}\)。连通可以用并查集。

代码:

const int _mxn=10000+5;
typedef int w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    bool operator<(node x) const {return w<x.w;}
    bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
int n,m;
//并查集部分
int f[_mxn],siz[_mxn];
void init(int n){
    for(int i=1;i<=n;i++)
        f[i]=i,siz[i]=1;
}
int find(int x)
{
    if(f[x]==x)
        return x;
    return f[x]=find(f[x]);
}
void merge(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)
    {
        f[fy]=fx;
        siz[fx]+=siz[fy];
    }
}
bool same(int x,int y){return find(x)==find(y);}
int size(int x){return siz[find(x)];}
//LCA 部分
int deep[_mxn],f1[_mxn][20],lg;
int dis[_mxn];
void dfs(int u,int fa,int d,int dep)
{
    dis[u]=d;
    deep[u]=dep;
    f1[u][0]=fa;
    for(int i=1;i<=lg;i++)
        f1[u][i]=f1[f1[u][i-1]][i-1];
    for(auto it:g[u])
    {
        if(it.v==fa)
            continue;
        dfs(it.v,u,d+it.w,dep+1);
    }
}
int lca(int x,int y)
{
    if(deep[x]<deep[y])
        swap(x,y);
    for(int i=lg;i>=0;i--)
        if(deep[f1[x][i]]>=deep[y])
            x=f1[x][i];
    if(x==y)
        return x;
    for(int i=lg;i>=0;i--)
        if(f1[x][i]!=f1[y][i])
            x=f1[x][i],y=f1[y][i];
    return f1[x][0];
}
int main()
{
    ___();
    int _;
    while(cin>>n>>m>>_)
    {
        init(n);
        for(int i=1;i<=n;i++)
            g[i].clear();
        lg=log2(n);
        while(m--)
        {
            int u,v,w;
            cin>>u>>v>>w;
            add(u,v,w),add(v,u,w);
            merge(u,v);
        }
        for(int i=1;i<=n;i++)//森林里每棵树都初始化
            if(find(i)==i)
                dfs(i,0,0,1);
        while(_--)
        {
            int u,v;
            cin>>u>>v;
            if(same(u,v))
                cout<<dis[u]+dis[v]-2*dis[lca(u,v)]<<endl;
            else
                cout<<"Not connected"<<endl;
        }
    }
    return 0;
}

C

题意

略,是 CF980E The Number Games

题解

10 分爆搜代码:

const int _mxn=1e6+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,k;
bool t[_mxn],vis[_mxn];
void dfs(int u,int fa)
{
    if(t[u])
        return;
    vis[u]=true;
    for(auto it:g[u])
    {
        if(it==fa)
            continue;
        dfs(it,u);
    }
}
void dfst(int dep,int cnt)
{
    if(dep>n)
        return;
    if(cnt==k)
    {
        memset(vis,false,sizeof(vis));
        for(int i=1;i<=n;i++)
        {
            if(!t[i])
            {
                dfs(i,0);
                break;
            }
        }
        bool f=true;
        for(int i=1;i<=n;i++)
            if(!t[i]&&!vis[i])
            {
                f=false;
                break;
            }
        if(f)
        {
            for(int i=1;i<=n;i++)
                if(t[i])
                    cout<<i<<" ";
            cout<<endl;
            exit(0);
        }
    }
    t[dep]=true;
    dfst(dep+1,cnt+1);
    t[dep]=false;
    dfst(dep+1,cnt);
}
int main()
{
    ___();
    cin>>n>>k;
    for(int i=2;i<=n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    dfst(1,0);
    return 0;
}

D

题意

略,是 [NOIP2016 提高组] 天天爱跑步 史诗弱化版。

题解

逆天数据,范围和原题一模一样,但是能 A 这题的代码在原题只有 25。

先求出每个点的 LCA,那么 \(u\)\(v\) 的路径就是 \(u\to\operatorname{LCA}(u,v)\to v\),然后从 \(u,v\) 分别推到 LCA,算出答案即可。

代码:

const int _mxn=299998+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,m,w[_mxn];
int ans[_mxn];
int deep[_mxn],f[_mxn][20],lg;
void init(int u,int fa,int dep)
{
    deep[u]=dep;
    f[u][0]=fa;
    for(int i=1;i<=lg;i++)
        f[u][i]=f[f[u][i-1]][i-1];
    for(auto it:g[u])
    {
        if(it==fa)
            continue;
        init(it,u,dep+1);
    }
}
int lca(int x,int y)
{
    if(deep[x]<deep[y])
        swap(x,y);
    for(int i=lg;i>=0;i--)
        if(deep[f[x][i]]>=deep[y])
            x=f[x][i];
    if(x==y)
        return x;
    for(int i=lg;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main()
{
    ___();
    cin>>n>>m;
    lg=log2(n);
    for(int i=2;i<=n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    init(1,0,1);
    for(int i=1;i<=n;i++)
        cin>>w[i];
    for(int i=1;i<=m;i++)
    {
        int s,t;
        cin>>s>>t;
        int lca1=lca(s,t);
        int len=deep[s]+deep[t]-2*deep[lca1];
        if(w[lca1]==deep[s]-deep[lca1])
            ans[lca1]++;
        int u=s,tc=0;
        while(u!=lca1)
        {
            if(w[u]==tc++)
                ans[u]++;
            u=f[u][0];
        }
        u=t,tc=len;
        while(u!=lca1)
        {
            if(w[u]==tc--)
                ans[u]++;
            u=f[u][0];
        }
    }
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<" ";
    cout<<endl;
    return 0;
}

E

题意

略,是 [APIO2010] 巡逻。(一次模拟赛出 3 道原题,臭名昭著

题解

正解肯定写不出来,所以考虑 \(K=1\) 时的部分分。题目说了,每条边都经过两次。很明显,在树上加一条边就会出现环,那么这个环上的边就只需要走一次,所以只需要最大化环的长度即可。于是想到树的直径,只需要在直径两端之间加一条边即可,最终答案即为 \((n-1)\times2-d+1\),其中 \(d\) 为直径长度。

30 分部分分代码:

const int _mxn=1e5+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,k;
int p,q,dis=0;
void dfs(int u,int fa,int d,bool f)//求直径
{
    if(d>dis)
    {
        dis=d;
        f?q=u:p=u;
    }
    for(auto it:g[u])
    {
        if(it==fa)
            continue;
        dfs(it,u,d+1,f);
    }
}
int main()
{
    ___();
    cin>>n>>k;
    for(int i=2;i<=n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    if(k==1)
    {
        //两遍 dfs 法求直径
        dis=0;
        dfs(1,0,0,false);
        dis=0;
        dfs(p,0,0,true);
        cout<<(n-1)*2-dis+1<<endl;//输出答案
    }
    else
    {
        cout<<(n-2)*2<<endl;//乱写的骗分
    }
    return 0;
}
posted @   电乔  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示