2023杭电多校 2023“钉耙编程”中国大学生算法设计超级联赛(1)

题目顺序不分难度

1005:用最小表示法求出每个串的最小字典序,然后用字符串哈希o1判断是不是相等即可

#include
using namespace std;
const int N = 2e5+10,mod=131;
typedef unsigned long long ULL;
int n,m;
ULL a[N];
char s[N];
void solve(){
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s", s+1);
        for(int j=m+1;j<=m+m;j++)
         s[j]=s[j-m];
        int I=1,J=2;
        while(J<=m&&I<=m){
            int k=0;
            while(ks[J+k]) I+=k+1;
            else J+=k+1;
            if(I==J) J++;
        }
        int k=min(I,J);
        a[i]=0;
        for(int j=0;j>t;
    while(t--) solve();
}

1009:每个组尽量平摊就行

#include
#include
#include
#include
#include 
#include
#include 
#include 
#include
using namespace std;
const int N=2e5+10,mod=1e9+7;
#define int long long
typedef long long LL;
int n,m;
void solve(){
    int n,m,d;
    cin>>n>>m>>d;
    if((m+n-1ll)/n>=d) cout<<"Yes\n";
    else cout<<"No\n";
}

signed main(){
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    int t=1;
    cin>>t;
    while(t--) solve();

    return 0;
}

1002:

一个经典的树形dp模型,求到每个点的最小代价

这里讲一下当复习了

f[u][2]表示当前点由自己看 儿子三个状态都能转移过来

f[u][1]表示儿子看守当前这个点,其中一个儿子看守即可

f[u][0]表示父亲看守这个点,儿子只能自己看或者他的孙子看儿子点了

#include
#include
#include
#include
#include 
#include
#include 
#include 
#include
using namespace std;
const int N=4e5+10,mod=1e9+7;
#define int long long
typedef long long LL;
int n,m;
int a[N];
vector g[N];
int f[N][3];
void dfs(int u,int fa)
{
    f[u][2] = a[u];
    int sum = 0;
    for (auto j:g[u])
    {
        if(j==fa) continue;
        dfs(j,u);
        f[u][0] += min(f[j][1], f[j][2]);
        f[u][2] += min(min(f[j][0], f[j][1]), f[j][2]);
        sum += min(f[j][1], f[j][2]);
    }

    f[u][1] = 1e18;
    for (auto j:g[u])
    {
        f[u][1] = min(f[u][1], sum - min(f[j][1], f[j][2]) + f[j][2]);
    }
}
void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++) f[i][0]=f[i][1]=f[i][2]=0;
    for(int i=1;i<=n;i++) g[i].clear();
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    dfs(1,-1);
    cout<>t;
    while(t--) solve();

    return 0;
}

1012:感觉这个题难在sg不在换根

首先只有一个点先手必输,当前状态是0,那么他父节点的sg函数是他儿子节点的+1

从博弈论出发父节点是当前全部儿子节点的sg函数的异或值

然后就换根地方了

从u到v?有啥变换 ,v的状态是全部儿子节点的异或,现在根变成v,u应该是v的儿子,

只要把这个变化量改了就行

#include
using namespace std;
const int N = 2e5+10,mod=1e9+7;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair PII;

int n,m;
int f[N],g[N];
vector G[N];
int qmi(int a, int k, int p)  // 求a^k mod p
{
    int res = 1 % p;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}
void dfs1(int u,int fa)
{
    f[u]=g[u]=0;
    for(auto v:G[u]){
        if(v==fa) continue;
        dfs1(v,u);
        f[u]^=(f[v]+1);
    }
}
void dfs2(int u,int fa){
    for(auto v:G[u]){
        if(v==fa) continue;
        g[v]=f[v]^((g[u]^(f[v]+1))+1);
        dfs2(v,u);
    }
}
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) G[i].clear();
    for(int i=1;i>a>>b;
        G[a].push_back(b);
        G[b].push_back(a);
    }
    dfs1(1,1);
    g[1]=f[1];
    dfs2(1,1);
    int cnt=0;
    for(int i=1;i<=n;i++) if(g[i]) cnt++;
    cout<<1ll*cnt*qmi(n,mod-2,mod)%mod<<"\n";
}

signed main(){
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    int t=1;
    cin>>t;
    while(t--) solve();
}

1001:

首先n点n-1条边还是连通图,是个树(好吧其实好像说了等于没说)

这个题其实就是一眼跟LCA有关的题,只是他要分析

第一个人的起点终点是sa,ta 第二个是sb tb

首先分析一下假设在x点相遇

第一个人在x点的时间可能是两个去终点的路中到x点

距离等于 k1*2*dist(sa,ta)+dist(sa,x) (就是不知道第几个来回的路上,起点到x点的路径)

也可能是终点回起点中到x点

距离等于 k1*2*dist(sa,ta)+dist(sa,x)+dist(sa,ta)

第二个人同理

k2*2*dist(sb,tb)+dist(sb,x)

k2*2*dist(sb,tb)+dist(sb,x)+dist(sb,tb)

相遇就是时间一样嘛0.0

联立方程求最小正整数解,用扩展欧几里得

我这里举例把

k1*2*dist(sa,ta)+dist(sa,x) =k2*2*dist(sb,tb)+dist(sb,x) 要他们相等

(dist都是已经知道的,可以通过lca,dist[a]+dist[b]-2*dist(lca(a,b)))

把2*dist(sa,ta)=a,2*dist(sb,tb)-b

方程等于 k1*a-k2*b=dist(sb,x)-dist(sa-x)

设dist(sb,x)-dist(sa-x)=d

k1*a-k2*b=d

设c=-b

k1*a+k2*c=d

根据贝祖定理

d%gcd(a,c)能整除才有解

然后你就直接用一个扩展欧几里得求出k1,k2即可

然后记得k1,k2别忘了乘d/gcd(a,c)倍

然后还有个问题,他要正整数解法

如果是负数咋办,你要减少k2那里,扩大k 

正数就扩大k2(k2那一坨是负数有符号),可以把k1减少,别变成负数就行

p=b/d,q=a/d,是因为(b,d)的最小公倍数是d

你要保持等式成立,只能加减共同的数

比如k1>=0

你直接把k1变成最小比如1?

(k1-1)/p,就是能保持符合等式且不会变成负数

好了可能有人看不懂...但是问题不大因为就算会了这题代码量也很大

代码我直接给题解的了...但是我还是不知道为啥他要用树剖不用倍增

#include
#include
#include
#define M 5005
using namespace std;
struct E{
	int to,nx;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b){
	edge[++tot].to=b;
	edge[tot].nx=head[a];
	head[a]=tot;
}
int sz[M],son[M],top[M],dep[M];
int Dfn[M],Low[M],tot_dfs;
int fa[M];
void dfs(int now){
	Dfn[now]=++tot_dfs;
	sz[now]=1;son[now]=0;
	for(int i=head[now];i;i=edge[i].nx){
		int nxt=edge[i].to;
		if(nxt==fa[now])continue;
		fa[nxt]=now;
		dep[nxt]=dep[now]+1;
		dfs(nxt);
		sz[now]+=sz[nxt];
		if(sz[son[now]]p2.a)val-=p2.a;
	int a=p1.a,b=-p2.a;
	int x,y,d=exgcd(a,b,x,y);
	if(val%d!=0)return 1e9;
	x*=val/d;y*=val/d;
	int p=b/d,q=a/d;
	if(x<0){
	int k=ceil((1.0-x)/p);
		x+=p*k,y-=q*k;
	}else if(x>=0){
		int k=(x-1)/p;
		x-=p*k,y+=q*k;
	}
	return a*x+p1.b;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		int n,m;
		tot=0;
		scanf("%d%d",&n,&m);
		tot_dfs=0;
		for(int i=1;i<=n;i++)head[i]=mark[i]=0;
		for(int i=1;idep[x2]){
				swap(a,c);
				swap(b,d);
				swap(x1,x2);
			}
			if(!In(a,x2)&&!In(b,x2)){
				puts("-1");
				continue;
			}
			int d1=dep[a]+dep[b]-2*dep[x1],d2=dep[c]+dep[d]-2*dep[x2];
			int p=a;
			while(1){
				Data[p][0]=(Point){2*d1,dep[a]-dep[p]};
				Data[p][1]=(Point){2*d1,2*d1-(dep[a]-dep[p])};
				mark[p]=step;
				if(p==x1)break;
				p=fa[p];
			}
			p=b;
			while(p!=x1){
				Data[p][0]=(Point){2*d1,d1-(dep[b]-dep[p])};
				Data[p][1]=(Point){2*d1,d1+(dep[b]-dep[p])};
				mark[p]=step;
				p=fa[p];
			}
			
			int ans_val=1e9,ans=-1;
			p=c;
			while(1){
				Point p1=(Point){2*d2,dep[c]-dep[p]};
				Point p2=(Point){2*d2,2*d2-(dep[c]-dep[p])};
				if(mark[p]==step){
					int res=1e9;
					res=min(min(Get_ans(p1,Data[p][0]),Get_ans(p1,Data[p][1])),min(Get_ans(p2,Data[p][0]),Get_ans(p2,Data[p][1])));
					if(res

1010:

首先找题目性质 Xi>=Xi-1

拆绝对值对于ai>x:ai=ai-x 不变

对于ai

1.ai=x1-ai

2.ai=x2-x1+ai

3.ai=x3-x2+x1-ai

且由于第一个性质,ai会一直小于X,可以发现每多一个数x,会改变ai的正负(ai=x2-x1+ai,不单单只表示ai,是除了X后面的那一堆)

所以考虑分开两个情况来维护

对于ai>=x:

维护总和 sum1

维护区间有多少个ai>=x的个数 cnt

维护懒标记 lazy3用来当前X需要减多少

维护当前区间最小值

对于共同维护 lazy2来维护当前数是加还是减(这个是不确定的要维护,比如对于两个相交的区间,不相交的地方可能是减法,但是相交由于两次减,会变成加这个区间的值)

对于ai

维护 总和 sum2

维护 lazy1也是懒标记用来维护每个区间需要加的值,这个是叠加的k值,所以计算sum2的时候

还要加上后面那一坨的值

好吧可能很多人会懵逼....但是可能要自己理解吧

#include 

#define ll long long
#define Max 200005

using namespace std;

struct Tree{
    ll lazy1,lazy2,lazy3,cnt,minn,sum1,sum2;
}st[Max*4];

int T,n,m,a[Max];

inline Tree up(Tree ls,Tree rs){
    Tree ans;
    ans.lazy1=ans.lazy3=0;ans.lazy2=1;
    ans.minn=min(ls.minn,rs.minn);
    ans.sum1=ls.sum1+rs.sum1;
    ans.sum2=ls.sum2+rs.sum2;
    ans.cnt=ls.cnt+rs.cnt;
    return ans;
}

inline void down(int node,int L,int R){
    int ls=node<<1,rs=node<<1|1;
    int mid=(L+R)>>1;

        st[ls].lazy3+=st[node].lazy3;
        st[rs].lazy3+=st[node].lazy3;
        st[ls].sum1-=st[node].lazy3*st[ls].cnt;
        st[rs].sum1-=st[node].lazy3*st[rs].cnt;
        st[ls].minn-=st[node].lazy3;
        st[rs].minn-=st[node].lazy3;

        st[ls].lazy1=st[node].lazy1+st[ls].lazy1*st[node].lazy2;
        st[rs].lazy1=st[node].lazy1+st[rs].lazy1*st[node].lazy2;
        st[ls].lazy2*=st[node].lazy2;
        st[rs].lazy2*=st[node].lazy2;
        st[ls].sum2=st[node].lazy1*(mid-L+1-st[ls].cnt)+st[ls].sum2*st[node].lazy2;
        st[rs].sum2=st[node].lazy1*(R-mid-st[rs].cnt)+st[rs].sum2*st[node].lazy2;
    st[node].lazy1=st[node].lazy3=0;
    st[node].lazy2=1;
    return;
}

inline void build(int node,int L,int R){
    if(L==R){
        st[node].lazy1=st[node].lazy3=0;
        st[node].lazy2=1;
        st[node].minn=st[node].sum1=a[L];
        st[node].sum2=0;
        st[node].cnt=1;
        return;
    }
    int mid=(L+R)>>1;
    build(node<<1,L,mid);
    build(node<<1|1,mid+1,R);
    st[node]=up(st[node<<1],st[node<<1|1]);
    return;
}

inline void change(int node,int l,int r,int L,int R,int k){
    if(L>=l&&R<=r){
        if(st[node].cnt){
            if(L==R){
                if(st[node].sum1>1;
                    change(node<<1,l,r,L,mid,k);
                    change(node<<1|1,l,r,mid+1,R,k);
                    st[node]=up(st[node<<1],st[node<<1|1]);
                }else{
                    st[node].lazy3+=k;
                    st[node].minn-=k;
                    st[node].sum1-=1ll*k*st[node].cnt;
                    st[node].lazy1=k-st[node].lazy1;
                    st[node].lazy2*=-1;
                    st[node].sum2=1ll*k*(R-L+1-st[node].cnt)-st[node].sum2;
                }
            }
        }else{
            st[node].lazy1=k-st[node].lazy1;
            st[node].lazy2*=-1;
            st[node].sum2=1ll*k*(R-L+1)-st[node].sum2;
        }
//        cout<>1;
    if(l<=mid)change(node<<1,l,r,L,mid,k);
    if(r>mid)change(node<<1|1,l,r,mid+1,R,k);
    st[node]=up(st[node<<1],st[node<<1|1]);
    return;
}

inline Tree query(int node,int l,int r,int L,int R){
    if(L>=l&&R<=r)return st[node];
    down(node,L,R);
    int mid=(L+R)>>1;
    if(r<=mid)return query(node<<1,l,r,L,mid);
    if(l>mid)return query(node<<1|1,l,r,mid+1,R);
    return up(query(node<<1,l,r,L,mid),query(node<<1|1,l,r,mid+1,R));
}

int main(){
    freopen("data3.in","r",stdin);
    freopen("data3.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
        cin>>n>>m;
        for(int i=1;i<=n;i++)cin>>a[i];
        build(1,1,n);
        for(int i=1;i<=m;i++){
            int opt;
            cin>>opt;
            if(opt==1){
                int l,r,x;
                cin>>l>>r>>x;
                change(1,l,r,1,n,x);
            }else{
                int l,r;
                cin>>l>>r;
                Tree ans=query(1,l,r,1,n);
                cout<

1003:

挺明显的区间dp吧

考虑最后一张牌出的类型

这里先说一下类型有m张牌 level个等级

所以类型有 m*level个不同状态

我们让状态为0是表示什么牌都没打出

f[l][r][p]表示区间【l,r】中最后打出牌的类型是p

考虑转移如果区间长度只有一个且什么牌都没打出(即状态为0),那么只能打出这唯一一张牌,贡献为a[l]

如果p不为0,即表示当最后牌打出的是p状态得牌贡献为0

对于一般区间

如果什么牌都没出:

1.分成两个不同区间

2.枚举最后打出的牌是1到m*level得牌

如果已经确定了最后打出什么牌

1.如果打出的牌最后等级为1,那么枚举区间中状态是p得牌,然后打出中间这一张牌,加上左右两个区间

2.如果打出的牌等级是P,那么你需要枚举分割点从两个区间获得最后打出的牌是等级比P小的且值是相同得两张牌

#include
using namespace std;
const int N = 510;
long long INF=1e18;
long long f[110][110][N];
long long val[N],a[N];
int n,maxl,m,P;
int Base[N];
long long get(int x){
    int level=(x-1)/m,b=(x-1)%m+1;
    return 1ll*Base[level]*val[b];
}
long long dfs(int l,int r,int p){
    if(l>r){
        if(p==0) return 0;
        else return -INF;
    }
    if(l==r){
        if(p==0) return val[a[l]];
        if(p==a[l]) return 0;
        return -INF;
    }
    if(f[l][r][p]!=-1) return f[l][r][p];
    long long res=-INF;
    if(p==0){//如果没要求
    //1.可以直接分开两个区间
        for(int mid=l;mid>n>>m>>maxl>>P;
    Base[0]=1;
    for(int i=1;i<=maxl;i++)
    Base[i]=1ll*Base[i-1]*P;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) cin>>val[i];
    cout<>t;
    while(t--) solve();
}

你可能感兴趣的:(算法)