2022暑期多校(牛客+杭电)

2022.7.18 牛客

A

队友写的

D

高中数学(

考虑弦长最大的时候,弧长最大。

于是画画图就会发现其实那个弦长最大的时候是OQ共线

然后连一下OAB,数学推导一下弦长,再通过弦长反推圆心角大小就好了。

记得别写反arcsinarccos

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
const double PI = 3.141592653589793;
int main(){
    cin>>T;
    while (T--){
        double R,x,y,d;
        scanf("%lf%lf%lf%lf",&R,&x,&y,&d);
        if(fabs(d-R) <=0.00001){
            printf("%.10lf\n",PI*R);
            continue;
        }
        double dis = sqrt(x*x+y*y);
        double t1=sqrt(R*R-(dis+d)*(dis+d));
        double t2=sqrt(R*R-(dis-d)*(dis-d));
        double Len=t2-t1;
        double Len1=sqrt(Len*Len+2.0*d*2.0*d);
        Len1=Len1/2.f;
        double Cos = Len1/R;
        double Angle = asin(Cos);
        double ans = 2.0 * Angle * R;
        printf("%.10lf\n",ans);
    }
    return 0;
}
复制代码

G

签到

显然填一堆9是最优的

然后记得判断原串是不是9999999x的形式就行了(

复制代码
#include <bits/stdc++.h>
using namespace std;
string ss;
int main(){
    cin>>ss;
    int Len=ss.length();
    if (Len == 1){
        cout<<ss;
        return 0;
    }
    bool flag=false;
    for (int i=0;i<Len-1;i++){
        if (ss[i]!='9') flag=true;
    }
    if (flag)
    for (int i=1;i<=Len-1;i++){
        printf("9");
    }else cout<<ss;
    return 0;
}
复制代码

I

血压暴涨题(

发现打牌轮数是固定的

直接dp就好了

然后记得打麻将的时候每一轮手上是13张牌

写错这个调了好久

dp[i][j]表示当前是第i轮,j个对子

每次可以多拿一个对子,或者不变

但是因为是最优策略,所以多拿一个对子的话,那张单牌一定还有3张在牌山里。

然后没了(

复制代码
#include <bits/stdc++.h>
using namespace std;
long long dp[170][10];
int T;
long long pai=136;
int Pai[5][15];
long long Ans[55]; 
string ss;
long long fish = 1e9+7;
long long Pow(int x,int y){
    long long ans=1;
    for (;y;y>>=1,x=1ll*x*1ll*x%fish)
        if (y&1) ans=1ll*ans*1ll*x%fish;
    return ans;
}
void Get(int x){
        memset(dp,0,sizeof(dp));
        pai = 136;
        pai = pai - 13;
        dp[0][x] = 1;
        for (int i=0 ; i<=pai ; i++)
            for (long long j=0;j<=6;j++){
                long long res = (13ll-2ll*j)*3ll;
                long long dan = pai-i;
                long long nw = 1ll*dp[i][j]*Pow(dan,fish-2)%fish;
                if (dan-res >= 0){
                    (dp[i+1][j+1] += 1ll*nw*res%fish)%=fish;
                    (dp[i+1][j] +=1ll*nw*(dan-res)%fish)%=fish;
                }
            }
        long long ans=0;
        for (int i=1;i<=pai-1;i++){
            ans = (ans + 1ll * dp[i][7] * i%fish)%fish;
        }
        Ans[x]=ans;
}
int cnt=0;
int main(){
    scanf("%d",&T);
    for (int i=0;i<=7;i++){
        Get(i);
    }
    while (T--){
        cnt++; 
        cin>>ss;
        memset(Pai,0,sizeof(Pai));
        int Len=ss.length();
        int x,hua,dui = 0;
        for (int i=0;i<Len;i++){
            if ('1' <= ss[i] && ss[i] <= '9'){
                x=ss[i]-'0';
                i++;
                if (ss[i] == 'm') hua = 1;
                if (ss[i] == 'p') hua = 2;
                if (ss[i] == 's') hua = 3;
                if (ss[i] == 'z') hua = 4;
                Pai[hua][x]++;
                if (Pai[hua][x] == 2){
                    dui++;
                }
            }
        }
        printf("Case #%d: %lld\n",cnt,Ans[dui]);
    }
    return 0;
}
复制代码

 补题:

C

首先,先考虑一个点的遮挡范围

把两个端点和这个点连起来,会形成一个三角区域

那就是它的遮挡范围

把线分为从上往下和从上往下两个方向

对于同一方向的线来说:

而显然,斜率越大,在同一行遮挡范围越大

那么其实,每一行只有第一个点是遮挡点

那么,每次就考虑把第一个点抓出来,维护一个最大斜率,对每一行算出最大斜率的遮挡位置,取个min

两个方向分开来算。

正着跑一遍,反着跑一遍即可。

具体可以画画图

 

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,M,K,T;
set<int> a[200005];
int mn[200005];
pair<int,int> nw[200005];
long long mn1[200005];
long long solve(){
    long long ans=0;
    double K=0;
    for (int i=1;i<=M;i++) mn1[i]=N;
    for (int i=1;i<=M;i++){
        if (i>1){
                double nK=double(i-1)/(double)mn[i];
                if (mn[i]!=N+1) K=max(K,nK);
                mn1[i] =int( min(double (N),min (double(mn1[i]),(i-1)/K - 0.000001) ));
            }
            else if (a[i].size()) mn1[i]=(*a[i].begin())-1; else mn1[i]=N;        
    }
    K=0;
        for (int i=M;i>=1;i--){
            if (i<M){
                double nK=double(M-i)/mn[i];
                if (mn[i]!=N+1) K=max(K,nK);
                mn1[i] =int( min(double(N),min (double(mn1[i]),(M-i)/K-0.0000001) ));
            }
            else if (a[i].size()) mn1[i]=min(mn1[i],(long long)(*a[i].begin())-1);
        ans+=1ll*mn1[i];
    }
    return ans; 
}
int main(){
    scanf("%d%d%d%d",&N,&M,&K,&T);
    for (int i=1;i<=K;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        a[y].insert(x);
        nw[i] = {x,y};
    }
    for (int i=1;i<=M;i++){
        if (a[i].empty()) mn[i]=N+1;
        else mn[i]=*a[i].begin();
    }
    while (T--){
        int id,x,y;
        scanf("%d%d%d",&id,&x,&y);
        int X=nw[id].first,Y=nw[id].second;
        a[Y].erase(X);
        if (a[Y].empty()) mn[Y]=N+1;
        else mn[Y] = *a[Y].begin();
        a[y].insert(x);
        nw[id]={x,y};
        mn[y]=*a[y].begin();
        printf("%lld\n",solve());
    }
    return 0; 
}
复制代码

 7.19杭电

B

墙<=15,随便搜搜就好了。

I


可以去判断所有可能的情况分类讨论

现场写的做法是每次随机两个点和他们处于的线是哪一条

如果所有的点都在这个线上,那么这样随机一次,两条都正确的概率是116

而错误率是1516

多随机几次就能把错误率降低到一个合适的范围。

复制代码
#include <bits/stdc++.h>
using namespace std;
bool flag=false;
int N;
int x[500005],y[500005];
bool vis[500005];
bool Check(int X,int Y){
    for (int i=1;i<=N;i++){
        if (x[i]==X || y[i]==Y || y[i]-Y==x[i]-X || x[i]+y[i] == X+Y)
            vis[i]=true;
    }
    for (int i=1;i<=N;i++)
        if (!vis[i]) return false;
    flag=true;
    return true;
}
int Random(int x){
    return 1ll*rand()*rand()%x*1ll*rand()%x+1;
}
int main(){
    int T;
    srand(time(NULL));
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        flag=false;
        for (int i=1;i<=N;i++)
            scanf("%d%d",&x[i],&y[i]);
        for (int i=1;i<=1000;i++){
            int TypeA=rand()%4+1,TypeB=rand()%4+1;
            while (TypeA == TypeB) TypeB=rand()%4+1;
            if (TypeA>TypeB) swap(TypeA,TypeB);
            for (int j=1;j<=N;j++)
                vis[j]=false;
            int P1=Random(N),P2=Random(N);
            int PointX,PointY;
            if (TypeA == 1 && TypeB == 2){
                PointX=x[P1],PointY=y[P2];
                Check(PointX,PointY);
                if (flag){
                    printf("YES\n");
                    break;
                }
            }
            if (TypeA == 1 &&TypeB==3){
                PointX=x[P1],PointY=PointX+y[P2]-x[P2];
                Check(PointX,PointY);
                if (flag){
                    printf("YES\n");
                    break;
                }
            }
            if (TypeA == 1 && TypeB == 4){
                PointX=x[P1],PointY=y[P2]+x[P2]-PointX;
                Check(PointX,PointY);
                if (flag){
                    printf("YES\n");
                    break;
                }
            }
            if (TypeA == 2 && TypeB == 3){
                PointX=x[P2]+y[P2]-PointY,PointY=y[P1];
                Check(PointX,PointY);
                if (flag){
                    printf("YES\n");
                    break;
                }
            }
            if (TypeA==2 && TypeB == 4){
                PointX=PointY-(y[P2]-x[P2]),PointY=y[P1];
                Check(PointX,PointY);
                if (flag){
                    printf("YES\n");
                    break;
                }
            }
            if (TypeA == 3 && TypeB == 4){
                PointY=(y[P1]+x[P1]+y[P2]-x[P2])/2;
                PointX=(y[P1]+x[P1]-y[P2]+x[P2])/2;
                if (Check(PointX,PointY)){
                    printf("YES\n");
                    break;
                    }
                }
            }
            if (!flag) printf("NO\n");
        }
    return 0;
}
复制代码

K

每个点的期望是12

算个逆元乘一下就好。

 

L

 

考虑alice每次都尽量的等分

考虑第i个数字,它如果进行i次等分后还有剩余,就说明bob

那么每次,i1后会变成i1,倒着加回去就行了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N;
int a[1000005];
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        for (int i=0;i<=N;i++){
            scanf("%d",&a[i]);
        }
        for (int i=N;i>=1;i--) a[i-1]+=a[i]/2;
        if (a[0] != 0) printf("Alice\n");
        else printf("Bob\n");
    }
    return 0;
}
复制代码

 赛后补题:

C

血压暴涨题

考虑dp[i][j][k]表示前i个物品,xor和为j,物品重量为k

dp[i][j][k]=dp[i1][j][k]ordp[i1][j^w[i]][kv[i]]

然后发现最后一维可以用bitset压掉,第一维滚动数组。

因为没有hdoj的号所以数据是手测的

复制代码
//dp[i][k][j] = dp[i-1][k^w[i]][j-a[i]] | dp[i-1][k][j]
#include <bits/stdc++.h>
using namespace std;
bitset<1060> dp[2][1060];
int v[1060],w[1060];
int main(){
    freopen("1003.in","r",stdin);
    freopen("1003.out","w",stdout);
    int T;
    scanf("%d",&T);
    while (T--){
        int N,M;
        scanf("%d%d",&N,&M);
        for (int i=0;i<=1;i++)
            for (int j=0;j<=1024;j++)
            dp[i][j].reset();
        for (int i=1;i<=N;i++)
            scanf("%d%d",&v[i],&w[i]);
        dp[0][0]=1;
        for (int i=1;i<=N;i++)
            for (int j=0;j<1024;j++){
                int o=i&1;
                dp[o][j] = dp[o^1][j];
                dp[o][j] |= (dp[o^1][j^w[i]])<<v[i];
            }
        int ans=-1;
        for (int i=0;i<1024;i++){
            if (dp[N&1][i][M]) ans=i; 
        }
        printf("%d\n",ans);
    }
}
复制代码

 7.23 牛客

队友都有事,我也有事,自己一个人打了三小时就下班了

G

构造题

构造方式是把1n序列分成k

每一段内逆序

然后得到的长度就是maxk,n/k

显然k=n

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
int T;
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        int Len =int((sqrt(N))+0.5);
        for (int i=0;i<N;i+=Len){
            if (i+Len-1 < N){
                for (int j=i+Len-1;j>=i;j--)
                    printf("%d ",j+1);
            }
            else{
                for (int j=N-1;j>=i;j--){
                    printf("%d ",j+1);
                }
            }
        }
        printf("\n");
    }
    return 0;
}
复制代码

J

线性回归方程

套式子就好了。

有点卡精度,自然数的和拆开一下,用longdouble

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
long double Pow(long double x){
    return x*x;
}
long double a[100006];
int main(){
    cin>>T;
    while (T--){
        long double N;
        scanf("%Lf",&N);
        long double ans=0;
        long double XY=0,SumX=0,SumY=0,SumX2=0;
        for (long double i=1;i<=N;i++){
            int x=i;
            scanf("%Lf",&a[x]);
            XY+=a[x]*i;
            SumX+=i;
            SumY+=a[x];
            SumX2+=i*i;
        }
        long double SumXX = SumX*SumX;
        long double AverY = SumY/N;
        long double b = (XY - (N+1)/2*SumY)/(N*(N+1)*(2*N+1)/6-(N+1)*(N+1)*N/4);
        long double aa = AverY - b*(N+1)/2;
        for (int i=1;i<=N;i++){
            ans = ans + Pow(a[i] - (b*i+aa));
        }
        printf("%.15Lf\n",ans);
    }
    return 0;
}
复制代码

K

 

(视为+1,)视为1

dp[i][j][k] a串考虑前i位,lcsj,前缀和为k

转移随便推推就出来了

答案在dp[M][N][0]

复制代码
#include <bits/stdc++.h>
using namespace std;
int dp[205][205][205];
const int fish = 1e9+7;
int main(){
    int T;
    cin>>T;
    while (T--){
        int N,M;
        string ss;
        scanf("%d%d",&N,&M);
        cin>>ss;
        for (int i=1;i<=M;i++)
            for (int j=0;j<=N;j++)
                for (int k=0;k<=100;k++)
                    dp[i][j][k] = 0;
        dp[0][0][0] = 1;
        for (int i=1;i<=M;i++)
            for (int j=0;j<=N;j++)
                for (int k=0;k<=100;k++){
                    if (j == 0){
                        dp[i][j][k] = dp[i-1][j][k+1]%fish;
                        if (k>0) (dp[i][j][k]+=dp[i-1][j][k-1])%=fish;
                    }
                    if (j!=0&&ss[j-1] == '('){
                        dp[i][j][k] = dp[i-1][j][k+1]%fish;
                        if (k-1 >= 0 && j-1>=0) (dp[i][j][k]+=dp[i-1][j-1][k-1])%=fish;
                    }
                    else if(j!=0) {
                        if (k-1 >=0) dp[i][j][k] = dp[i-1][j][k-1]%fish;
                        if (j-1 >= 0)(dp[i][j][k]+=dp[i-1][j-1][k+1])%=fish; 
                    }
                //cout<<i<<" "<<j<<" "<<k<<" "<<dp[i][j][k]<<endl;
            }
        printf("%d\n",dp[M][N][0]%fish);
    }
    return 0;
}
复制代码

 

补题:

L

时间不够根本没看,结果其实是简单题(

首先题目要求连续段,肯定是考虑这个连续段的左右端点

不妨设dp[i][j]表示在i号世界,能到达第j个点的最右左端点

转移的话就两种,一种情况是不动,一种情况是走边

但是需要注意的是,1号点无论在上一个世界还是这一个世界,都视为在第i个世界

复制代码
//dp[i][j]表示考虑到第i个世界,当前在j号点,最晚从哪个地方开始 
//dp[i][y] = max{dp[i-1][y],dp[i-1][x]} x->y 存在
//ans = max i-dp[i][T]+1 
#include <bits/stdc++.h>
using namespace std;
int dp[2][10005];
int N,M;
int ans=1e9+7;
int main(){
    scanf("%d%d",&N,&M);
    for (int i=1;i<=N;i++){
        int L;
        int nw = i&1;
        int lst =1-i&1;
        scanf("%d",&L);
        dp[nw][1] = i;
        dp[lst][1] = i;
        for (int j=2;j<=N;j++)
            dp[nw][j] = dp[lst][j];
        for (int i=1;i<=L;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            dp[nw][y] = max(dp[nw][y] , dp[lst][x]); 
        }
        if (dp[nw][M]!=0) ans = min(ans,i-dp[nw][M]+1);
    }
    if (ans == 1e9+7) cout<<-1;
    else cout<<ans;
    return 0;
} 
复制代码

 C

读懂题目,然后二分+SPFA找个环就好了

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,M;
int cnt,las[10005],nex[10005],Arrive[10005];
long double dis[10005],qz1[10005],qz[10005];
void Add(int x,int y,long double v){
    cnt++;
    nex[cnt]=las[x];
    las[x]=cnt;
    Arrive[cnt]=y;
    qz[cnt]=v;
}
int Times[10005];
bool vis[10005];
bool Check(long double K){
    for (int i=1;i<=M;i++)
        qz1[i] = qz[i] *K;
    queue<int> SP;
    memset(Times,0,sizeof(Times));
    for (int i=1;i<=N;i++){
        SP.push(i);
        dis[i]=1;
        vis[i]=true;
    }
    while (!SP.empty()){
        int nw = SP.front();
        SP.pop();
        if (Times[nw] > N) return true;
        vis[nw] = false;
        for (int i=las[nw];i;i=nex[i])
            if (dis[Arrive[i]] < dis[nw] * qz1[i]){
                dis[Arrive[i]] =dis[nw] * qz1[i];
                Times[Arrive[i]] =Times[nw] +1;
                if (!vis[Arrive[i]]){
                    SP.push(Arrive[i]);
                    vis[Arrive[i]] = true;
                }
            }
    }
    return false;
}
int main(){
    scanf("%d%d",&N,&M);
    for (int i=1;i<=M;i++){
        int b,d;
        long double a,c;
        scanf("%Lf%d%Lf%d",&a,&b,&c,&d);
        Add(b,d,1.0*c/a);
    }
    long double l=0,r=1;
    while (r-l>1e-9){
        double mid=(l+r)/2;
        if (Check(mid)) r=mid;
        else l=mid; 
    }
    printf("%.10Lf\n",l);
    return 0;
}
复制代码

 7.25牛客


不会SAM输麻了

A

实际上考虑删除什么点会导致lca变化

一定是某个点,它下面挂着两个子树,一个有K1个关键点,另一个有一个关键点,或者本身是一个关键点

然后两个树分别跑一遍treedp去找这些能删的点就好了。

具体看代码(

复杂度O(n)

实际上有个复杂度带一个log但代码非常好写的做法(

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,K;
int va[100005],vb[100005];
vector<int> A[100005],B[100005];
bool flaglca;
int Son[100005];
bool flaga,flagb;
bool flag1=false;
int wha[100005],whb[100005],lcaA,lcaB,asA,asB,WhoA,WhoB,flaglcb,x[100005];
bool pda[100005],pdb[100005];
void dfs1(int Now,int fa){
    bool pd1=false,pd2=false;
    if (pda[Now]) { Son[Now] = 1;wha[Now] = Now;}
    for (auto Arr:A[Now]){
        dfs1(Arr,Now);
        Son[Now] += Son[Arr];
        if (Son[Arr] == 1) wha[Now] = wha[Arr];
        if (Son[Arr] == K-1) pd1=true;
        if (Son[Arr] == 1) pd2=true;
    }
    if (Son[Now] == K && !flaglca){
        flaglca=true;
        lcaA=Now;
    }
    if (Son[Now] == K-1 && !flag1) {
        flag1=true;
        asA=Now;
    }
    if (pd1 && pd2 && !flagb) {
        flagb=true;
        WhoA=wha[Now];
    }
    if (!flagb&&pd1 && pda[Now]){
        flagb=true;
        WhoA=Now;
    }
}
void dfs2(int Now,int fa){
    bool pd1=false,pd2=false;
    if (pdb[Now]) { Son[Now] = 1;whb[Now] = Now;}
    for (auto Arr:B[Now]){
        dfs2(Arr,Now);
        Son[Now] += Son[Arr];
        if (Son[Arr] == 1) whb[Now] = whb[Arr];
        if (Son[Arr] == K-1) pd1=true;
        if (Son[Arr] == 1) pd2=true;
    }
    if (Son[Now] == K && !flaglcb){
        flaglcb=true;
        lcaB=Now;
    }
    if (Son[Now] == K-1 && !flag1) {
        flag1=true;
        asB=Now;
    }
    if (pd1 && pd2 && !flagb) {
        flagb=true;
        WhoB=whb[Now];
    }
    if (!flagb&&pd1 && pdb[Now]){
        flagb=true;
        WhoB=Now;
    }
}
int main(){
    scanf("%d%d",&N,&K);
    for (int i=1;i<=K;i++){
        scanf("%d",&x[i]);
        pda[x[i]]=true;
        pdb[x[i]]=true;
    }
    for (int i=1;i<=N;i++)
        scanf("%d",&va[i]);
    for (int i=2;i<=N;i++){
        int fa;
        scanf("%d",&fa);
        A[fa].push_back(i);
    }
    for (int i=1;i<=N;i++)
        scanf("%d",&vb[i]);
    for (int i=2;i<=N;i++){
        int fa;
        scanf("%d",&fa);
        B[fa].push_back(i);
    }
    if (K==2){
        int ans=0;
        if (va[x[1]]>vb[x[1]]) ans++;
        if (va[x[2]]>vb[x[2]]) ans++;
        printf("%d\n",ans);
        return 0;
    }
    dfs1(1,1);

    memset(Son,0,sizeof(Son));
    flag1=false;
    flagb=false;
    dfs2(1,1);
    int ans=0;
    if (WhoA == 0 && WhoA == 0){
        if (va[lcaA] > vb[lcaB]) printf("%d\n",K);
        else printf("%d\n",0);
        return 0;
    }
    if (WhoA == WhoB){
        if (WhoA!=0){
        if (va[lcaA]>vb[lcaB]) ans += K-1;
        if (va[asA] > vb[asB]) ans++;
        cout<<ans;
        return 0;
        }
        else if (va[lcaA]>vb[lcaB]) printf("%d\n",K);else printf("0\n");
        return 0;
    }
    //cout<<"qwq"<<endl;
    if (WhoA != WhoB){
        if (va[lcaA] > vb[lcaB]) ans += K-2;
        if (WhoA!=0){
            if (va[asA]>vb[lcaB]) ans++;
        }
        else if (WhoA==0&&va[lcaA] > vb[lcaB]) ans++;
        if (WhoB!=0){
            if (va[lcaA]>vb[asB]) ans++;
        }else if (WhoB == 00&&va[lcaA] > vb[lcaB]) ans++;
        cout<<ans;
        return 0;
    }
    return 0;
}

复制代码

C

它的本意大概是想让人写个trie然后线性吧

但是它没卡成一个log的做法

所以直接排序即可(

复制代码
#include <bits/stdc++.h>
using namespace std;
string ss[2000005];
bool temp(string s1,string s2){
    if (s1.length()==s2.length()) return s1<s2;
    else {
        string ss3=s1+s2,ss4=s2+s1;
        return ss3<ss4;
    }
}
int main(){
    int N;
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        cin>>ss[i];
    }
    sort(ss+1,ss+N+1,temp);
    for (int i=1;i<=N;i++)
        cout<<ss[i];
    return 0;
}
复制代码

J


讲完思路扔给队友了不过队友没写完,不过也没差了(

做法大概是考虑把路当成点,然后在路口考虑方向跑一个bfs

但是这个bfs因为有右转0贡献的存在,所以要开双端队列

代码等队友补完再说吧(

补题

H

不会SAM输麻了…

考虑题目在说个什么东西

其实本质上就是求,在BA的公共子串的前提下的最大子段和

而每个位置的公共子串长度是经典的SAM问题,把S串建个SAM,把B串扔上去跑,能跳就跳,不能跳就删除首字符继续跳。

然后最大子段和问题可以用单调队列O(n)处理

然后就结束了

当个板子记录吧(

复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int N,M,K;
string ss;
int lst,tot,len[300005],fa[300005],tr[300005][28];
void Extend(int nw){
    int x = ++tot,u=lst,v;
    lst = tot;
    len[x] = len[u] + 1;
    for (;u && !tr[u][nw]; tr[u][nw] = x,u = fa[u]);
    if (!u) fa[x] = 1;
    else if (len[u]+1 == len[v = tr[u][nw]]) fa[x] = v;
    else{
    int w = ++tot;
    len[w] = len[u]+1,fa[w] = fa[v];
    memcpy(tr[w],tr[v],sizeof(tr[v]));
    for (fa[v] = fa[x] = w ; u && tr[u][nw] == v ; tr[u][nw]=w,u=fa[u]);
    }    
}

ll Sum[300005];
int stk[300005];
void Solve(){
    char ss[100005];
    cin>>ss+1;
    int Len = M;
    int nw = 1,L = 0;
    int h=0,t=0;
    int x=1;
    stk[0] = 0; 
    ll ans = 0;
    for (int i=1;i<=Len;i++){
        while (x && !tr[x][ss[i] - 'a']) {x = fa[x],L = len[x];}
        if (!x) x=1,L=0;
        else x = tr[x][ss[i]-'a'],++L;
        while(h<=t && Sum[stk[t]] >= Sum[i]) t--;
        stk[++t] = i;
        while (h<=t && stk[h]< i - L) h++;
        ans = max(ans,Sum[i] - Sum[stk[h]]); 
    }
    printf("%lld\n",ans); 
}
int main(){
    scanf("%d%d%d",&N,&M,&K);
    string ss;
    cin>>ss;
    tot = lst = 1;
    int Len  = ss.length();
    for (int i = 0 ;i< Len; i++) Extend(ss[i] - 'a');
    for (int i = 1;i<=M;i++){
        int x;
        scanf("%d",&x);
        Sum[i] = Sum[i-1]+x;
    }
    for (int i=1;i<=K;i++){
        Solve();
    }
}
复制代码

 

7.26 杭电

1002

 

二分出boss被击败的时间,然后里面直接写一个状压dp即可

因为二分完时间后就知道每个技能能造成多少伤害了。

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int N = 1e5 + 5;
const int INF = 2e6;

int n, t[N], len[N], tt[N * 3];
ll H, d[20][N], dp[N * 3];

bool check(int x) {
    for (int i = 1; i < (1 << n); ++i) {
        dp[i] = 0;
        for (int j = 1; (1 << (j - 1)) < (1 << n); j++) {
            if ((1 << (j - 1)) & i) { // subset
                int k = i ^ (1 << (j - 1));
                tt[i] = tt[k] + t[j];
                if (tt[k] >= x)
                    continue;
                dp[i] = max(dp[i], dp[k] + d[j][min(len[j], x - tt[k])]);
                // cerr << i << " " << j << " " << len[j] << " " << x << " " << tt << " " << min(len[j] - 1, x - tt) << endl;
            }
        }
        if (dp[i] >= H)
            return true;
    }
    // for (int i = 1; i < (1 << n); ++i)
    //     cout << dp[i] << " ";
    // cout << endl;
    return false;
}

void solve() {
    cin >> n >> H;
    for (int i = 1; i <= n; ++i) {
        cin >> t[i] >> len[i];
        for (int j = 1; j <= len[i]; ++j) {
            cin >> d[i][j];
            d[i][j] += d[i][j - 1];
        }
    }
    int l = 0, r = INF;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    cout << (l == INF ? -1 : l - 1) << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T; cin >> T;
    while (T--)
        solve();
    return 0;
}
复制代码

 

1003

签到题

复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
using namespace std;


int main()
{
    int t;
    cin >> t;
    
    char c=getchar();
    char str[100024];
    while(t--)
    {
        cin.get(str,100024);
        cin.get();
        printf("%c",(char)str[0]-'a'+'A');
        for(int i=1;i<strlen(str)-1;i++)
            if(str[i-1]==' ')
            printf("%c",(char)str[i]-'a'+'A');

        cout << endl;
    }
    return 0;
}
复制代码

1009


有个显然的贪心,能不取则不取,直到一定要取

拿个set维护这个过程就好了。

复制代码
#include <bits/stdc++.h>
using namespace std;
struct Node{
    int l,r;
}a[100005];
int N,K;
bool temp(Node a,Node b){
    return ((a.l<b.l)||(a.l == b.l&&a.r<b.r));
}
set<int> Q;
void Solve(){
    scanf("%d%d",&N,&K);
    for (int i=1;i<=N;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        a[i]={x,y};
    }
    sort(a+1,a+N+1,temp);
    int ans=0;
    for (int i=1;i<=N;i++){
        while (!Q.empty() && *Q.begin()<a[i].l){
            int Times=K;
            while (Times--)
                if (!Q.empty()){
                    Q.erase(Q.begin());
            }
        ans++;
        }
        Q.insert(a[i].r);
    }
    while (!Q.empty()){
        int Times=K;
            while (Times--)
                if (!Q.empty()){
                    Q.erase(Q.begin());
            }
        ans++;
    }
    printf("%d\n",ans);
    Q.clear();
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        Solve();
    }
    return 0;
}
复制代码

1012


dp[i][j]表示S中前i个数,A匹配到了j的方案数

dp[i][j]=dp[i][j1] si==aj

dp[i][j]=dp[i1][j] si==bij

然后通过这个转移会发现其实每次有用的状态只有两个

for一遍就好。

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int N = 3e5 + 5;
const int INF = 0x3f3f3f3f;
const int MOD = 998244353;

int n, P[N], Q[N], S[N * 2], t[N];
ll dp[2][N];

void solve() {
    cin >> n;
    for (int i = 0; i <= n; ++i)
        dp[0][i] = dp[1][i] = t[i] = 0;
    for (int i = 1; i <= n; ++i) {
        int p; cin >> p;
        P[p] = i;
    }
    for (int i = 1; i <= n; ++i) {
        int q; cin >> q;
        Q[q] = i;
    }
    for (int i = 1; i <= n * 2; ++i)
        cin >> S[i];
    int o = 1;
    dp[0][0] = 1;
    for (int i = 1; i <= n * 2; ++i, o ^= 1) {
        // for (int j = 0; j <= n; ++j)
        //     dp[o][j] = 0;
        // dp[o][P[S[i]]] = (dp[o ^ 1][P[S[i]] - 1] + dp[o ^ 1][i - Q[S[i]]]) % MOD;
        dp[o][P[S[i]]] = 0;
        if (i - Q[S[i]] <= n && i - Q[S[i]] >= 0)
            dp[o][i - Q[S[i]]] = 0;
        if (t[P[S[i]] - 1] == i - 1)
            dp[o][P[S[i]]] += dp[o ^ 1][P[S[i]] - 1];
        if (i - Q[S[i]] >= 0 && i - Q[S[i]] <= n && t[i - Q[S[i]]] == i - 1)
            dp[o][i - Q[S[i]]] += dp[o ^ 1][i - Q[S[i]]];
        dp[o][P[S[i]]] %= MOD;
        if (i - Q[S[i]] <= n && i - Q[S[i]] >= 0)
            dp[o][i - Q[S[i]]] %= MOD;
        t[P[S[i]]] = i;
        if (i - Q[S[i]] <= n && i - Q[S[i]] >= 0)
            t[i - Q[S[i]]] = i;
    }
    if (t[n] == n * 2)
        cout << dp[o ^ 1][n] % MOD << endl;
    else
        cout << 0 << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T; cin >> T;
    while (T--)
        solve();
    return 0;
}
复制代码

 1011

犯蠢大题(

首先把曼哈顿距离转为切比雪夫距离

然后考虑二分答案

二分的时候,考虑以x答案的时候,需要满足的是:min(max(abs(xix),abs(yiy)),wi)<x

也就意味着,切比雪夫距离和距离wi都要比x

不妨假设我们现在二分一个wi,让它作为x

然后这题就变成,考虑所有权比wi大的数,是否都存在比wi大的距离

那么我们显然只要考虑四个点,即切比雪夫坐标下后缀最左,最右,最上,最下的点与这个点的距离。这个一个后缀minmax就搞定了。

然后我们每次二分wi,更新答案,然后如果没有就往小了找,有就往大了找。

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N,Q;
int ans=0;
int w1[300005],Mnx[300005],Mxx[300005],Mxy[300005],Mny[300005];
int x[300005],y[300005],w[300005];
int Check(int x,int X,int Y){
    bool flag=false;
    int anss=-2e9;
    anss = max(anss,min(w1[x],abs(Mxx[x]-X)));
    anss = max(anss,min(w1[x],abs(Mxy[x]-Y)));
    anss = max(anss,min(w1[x],abs(Y-Mny[x])));
    anss = max(anss,min(w1[x],abs(X-Mnx[x])));
    ans=max(ans,anss);
    if (abs(Mxx[x]-X) >= w1[x])
    flag=true;
        //if (x==0) cout<<anss<<" "<<"qwq"<<endl; 
    if (abs(Mxy[x]-Y)>= w1[x])
    flag=true;
        //if (x==0) cout<<anss<<" "<<"qwqq"<<" "<<Mxy[x]-Y<<endl; 
    if (abs(Y-Mny[x]) >= w1[x])
    flag=true;        
    if (abs(X - Mnx[x]) >= w1[x])flag=true;
    //if (x==0) cout<<min(w1[x],Y-Mny[x])<<endl;
    if (flag) return anss;
    else return -1;
}
int main(){
    //freopen("1011.in","r",stdin);
    //freopen("data.out","w",stdout);
    scanf("%d",&T);
    while (T--){
        scanf("%d%d",&N,&Q);
        int NN=N;
        for (int i=1;i<=N;i++){
            scanf("%d%d%d",&x[i],&y[i],&w[i]);
            int nx,ny;
            nx=x[i]+y[i];
            ny=x[i]-y[i];
            x[i]=nx,y[i]=ny;
        }
        for (int i=1;i<=N;i++)
            w1[i]=w[i];
        sort(w1+1,w1+N+1);
        N=unique(w1+1,w1+N+1)-w1-1;
        for (int i=0;i<=N;i++)
            Mnx[i]=Mny[i]=2e9+7,Mxx[i]=Mxy[i]=-2e9;
        for (int i=1;i<=NN;i++){
            int pos = lower_bound(w1+1,w1+N+1,w[i])-w1;
            Mxx[pos] = max(Mxx[pos],x[i]);
            Mxy[pos] = max(Mxy[pos],y[i]);
            Mnx[pos] = min(Mnx[pos],x[i]);
            Mny[pos] = min(Mny[pos],y[i]);
        }
        for (int i=N-1;i>=0;i--){
            int pos=i;
            Mxx[pos]=max(Mxx[pos],Mxx[pos+1]);
            Mxy[pos]=max(Mxy[pos],Mxy[pos+1]);
            Mny[pos]=min(Mny[pos],Mny[pos+1]);
            Mnx[pos]=min(Mnx[pos],Mnx[pos+1]);
        }
        //cout<<Mnx[0]<<endl;
        w1[0] = 2e9+7;
        while (Q--){
        int l=1,r=N,xx,yy;
        
        scanf("%d%d",&xx,&yy);
        int X,Y;
        X=xx+yy;
        Y=xx-yy;
        xx=X,yy=Y;
        while (l<=r){
            //cout<<mid<<endl;
            int mid = (l+r)>>1;
            if (Check(mid,xx,yy)!=-1) ans=max(ans,Check(mid,xx,yy)),l=mid+1;
            else r=mid-1;
            }
            printf("%d\n",ans);
        }
    }
}
复制代码

 补题:

1008

感性理解一下,最后答案平面一定是由所有线段的某三个端点决定的,所以枚举三个端点暴力判断即可。

记得判断一下共线的情况

判断线段与平面关系用混合积,判断两端点和平面的位置关系

O(n4)

复制代码
#include <bits/stdc++.h>
using namespace std;
struct Node {
    int x,y,z;
    Node(){}
    Node(int a,int b,int c){x=a,y=b,z=c;}
    Node operator - (const Node &p) const{return Node(x-p.x,y-p.y,z-p.z);}
    Node operator * (const Node &p) const{return Node(y*p.z-z*p.y,z*p.x-x*p.z,x*p.y-y*p.x);}
    int operator ^ (const Node &p) const{return x*p.x+y*p.y+z*p.z;}
    bool operator == (const Node &p) const {return (x==p.x && y==p.y && z==p.z);} 

}a[505];    
void output(Node nw){
    cout<<nw.x<<" "<<nw.y<<" "<<nw.z<<endl;
}
bool CoLine(Node a,Node b,Node c){
    Node nw = (c-a)*(c-b);
    return (nw.x==0 &&nw.y ==0 && nw.z==0);
}
int GetSide(Node a,Node b,Node c,Node d){
    return ((b-a)*(c-a))^(d-a);//混合积 
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        int ans=0;
        int N;
        scanf("%d",&N);
        for (int i=0;i<2*N;i++){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            a[i].x=x;
            a[i].y=y;
            a[i].z=z;
        }
        for (int i=0;i<2*N;i++)
            for (int j=0;j<i;j++){
                if (a[i] == a[j]) continue;
                for (int k=0;k<j;k++){//
                    if (i==4&& j==1 && k==0 && a[i] == a[k]) {
                        output(a[i]);output(a[k]);
                    }
                    if (a[j] == a[k] || a[i] == a[k]) continue;
                
                    if (CoLine(a[i],a[j],a[k])) continue; 
                    int nww=0;
                    for (int nw=0;nw<N;nw++){
                        int x = GetSide(a[i],a[j],a[k],a[nw<<1]);
                        int y = GetSide(a[i],a[j],a[k],a[nw<<1|1]);
                        if (x==0 || y==0 ||1ll*x*y<0) nww++;
                    }
                    ans=max(nww,ans);
                }
                int nww=0;
                for (int k=0;k<N;k++)
                    if (CoLine(a[i],a[j],a[k<<1])|| CoLine(a[i],a[j],a[k<<1|1])) nww++;
                ans=max(ans,nww);
            }
        printf("%d\n",ans);
    }
    return 0;
}
复制代码

7.28 杭电

1001

区间dp,写dp的队友现场有点事情,回头补(

1002

图论

显然考虑先扫一遍把dis跑出来,然后转移方向就确定了

然后在新图上跑个最长路就好了。

0环问题用tarjan缩点解决。

代码还没写完(

1004


签到,输出一串no

打表或者具体证明都还行

复制代码
#include <bits/stdc++.h>
using namespace std;
int main(){
    int T,N;
    scanf("%d",&T);
    while(T--){
        int N;
        scanf("%d",&N);
        printf("No\n");
    }
    return 0;
}
复制代码

1006

签到,阅读理解读清楚就好。

复制代码
#include <bits/stdc++.h>
using namespace std;
double ans1,ans2;
int N;
int a[100005];
int main(){
    int T;
    cin>>T;
    while (T--){
        ans1=ans2=0;
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            scanf("%d",&a[i]);
            if (ans1 <=100 && ans1 + a[i]>=100){
                double xx = ans1+a[i]-100;
                if (ans1 + xx *0.8 <=200) ans1=100+xx*0.8;
                else{
                    double Ned = (100 - ans1);
                    double xxx = a[i] - Ned - 125;
                    ans1 = ans1 + Ned + 100 + xxx*0.5;
                }
            }else
            if (ans1 < 200 && ans1>=100 && ans1 + 0.8*a[i] >=200){
                double Ned = 200-ans1;
                double xxx = Ned / 0.8;
                double nw = a[i] - xxx;
                ans1 = 200 + nw*0.5;
            }else
            if (ans1<200 && ans1>=100){
                ans1 = ans1 + a[i] *0.8;
            }
            else if (ans1 >= 200) ans1 = ans1 + a[i]*0.5;
            else if (ans1 <=100) ans1 = ans1 + a[i];
            if (ans2<200 && ans2 >=100)
                ans2 = ans2 + a[i] * 0.8;else
            if (ans2 >=200) ans2 = ans2 + a[i] * 0.5;else
            if (ans2 <100) ans2 = ans2 + a[i];
            //cout<<ans1<<endl;
        }
        printf("%.3lf %.3lf\n",ans1,ans2);
    }
    return 0;
}
复制代码

1007

出题人锐评:比较签(

考虑题目的本质,其实一定是往前跳一次,然后不断地一步一步往回跳

然后题目其实就转化为把序列分成一堆段,然后相邻两段之间的长度和<=K

写一下式子,假设当前这段从i开始,当前攻击力为atk,到j结束

那么对每一个ij之间的k就要满足sumjsumk+atk>ak

移项

sumj+atk>sumk+ak

sumk+ak 取最大的,sumj枚举,atk已知

能取就直接划一刀,因为这样能给下一段留出更多的长度空间。

复制代码
#include <bits/stdc++.h>
using namespace std;
double ans1,ans2;
int N;
int a[100005];
int main(){
    int T;
    cin>>T;
    while (T--){
        ans1=ans2=0;
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            scanf("%d",&a[i]);
            if (ans1 <=100 && ans1 + a[i]>=100){
                double xx = ans1+a[i]-100;
                if (ans1 + xx *0.8 <=200) ans1=100+xx*0.8;
                else{
                    double Ned = (100 - ans1);
                    double xxx = a[i] - Ned - 125;
                    ans1 = ans1 + Ned + 100 + xxx*0.5;
                }
            }else
            if (ans1 < 200 && ans1>=100 && ans1 + 0.8*a[i] >=200){
                double Ned = 200-ans1;
                double xxx = Ned / 0.8;
                double nw = a[i] - xxx;
                ans1 = 200 + nw*0.5;
            }else
            if (ans1<200 && ans1>=100){
                ans1 = ans1 + a[i] *0.8;
            }
            else if (ans1 >= 200) ans1 = ans1 + a[i]*0.5;
            else if (ans1 <=100) ans1 = ans1 + a[i];
            if (ans2<200 && ans2 >=100)
                ans2 = ans2 + a[i] * 0.8;else
            if (ans2 >=200) ans2 = ans2 + a[i] * 0.5;else
            if (ans2 <100) ans2 = ans2 + a[i];
            //cout<<ans1<<endl;
        }
        printf("%.3lf %.3lf\n",ans1,ans2);
    }
    return 0;
}
复制代码

1011

关于sol写了一大堆结果我其实只推了其中一部分就莽了上去这件事

首先,对于一个长度为偶数的区间来说,做两次操作一定能把区间全变成0

然后其实我们每次可以取两个,做两次,得到两个0

然后就会发现,我们可以选择保留一部分数字,或者不保留一部分数字

因为0的出现可以使数字的位置进行更换

于是问题就变成了取这些数字中的一部分,让它们xor最大。

然后差点不会了(

这个东西套一个线性基就好了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N;
#define ll long long
long long d[65];
void add(ll x)
{
    for(int i=60;i>=0;i--)
    {
        if(x&(1ll<<i))
        {
            if(d[i])x^=d[i];
            else
            {
                d[i]=x;
                break;
            }
        }
    }
}
ll ans()
{
    ll anss=0;
    for(int i=60;i>=0;i--)
    if((anss^d[i])>anss)anss^=d[i];
    return anss;
}

int main(){
    scanf("%d",&T);
    while (T--){
        memset(d,0,sizeof(d)); 
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            long long x;
            scanf("%lld",&x);
            add(x);
        }
        printf("%lld\n",ans());
    }
}
复制代码

赛后补题 

1003

实际上读懂题目之后,把不等式一写就是差分约束板题了(

复制代码
#include<bits/stdc++.h>
using namespace std;
#define inf 1e9+7
int cnt,las[30005],dis[30005],Times[30005],nex[30005],Arrive[30005],qz[30005];
bool Is[30005];
void Clear(int mx){
    cnt=0;
    for (int i=1;i<=mx;i++)
        las[i]=0;
}
void jt(int x,int y,int val){
    cnt++;
    nex[cnt]=las[x];
    las[x]=cnt;
    Arrive[cnt]=y;
    qz[cnt]=val;
}
bool Loop=0;
void SPFA(int S,int NN){
    deque<int> Bfs;
    for (int i=1;i<=NN;i++){
        dis[i] = inf;
        Is[i]=false;
        Times[i]=0;
    }
    Bfs.push_front(S);
    dis[S]=0;
    Is[S]=true;
    Times[S]=1;
    while (!Bfs.empty()){
        int nw = Bfs.front();
        Bfs.pop_front();
    //    cout<<nw<<endl;
        for (int i=las[nw];i;i=nex[i]){
            int v=Arrive[i];
            if (dis[v] > dis[nw] + qz[i] && dis[nw] != inf){
                dis[v] = dis[nw] + qz[i];
                if (!Is[v]){
                    Is[v]=true;
                    if (!Bfs.empty() && dis[v] < dis[Bfs.front()]){
                        Bfs.push_front(v);
                        Times[v] ++;
                        if (Times[v] == NN) {
                            Loop = -1;
                            return;
                        }
                    }
                    else{
                        Bfs.push_back(v);
                        Times[v] ++;
                        if (Times[v] == NN) {
                            Loop = -1;
                            return;
                        }
                    }
                }
            }
        }
        Is[nw] = false;
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        int N,K;
        Loop=0;
        scanf("%d%d",&N,&K);
        for (int i=1;i<=N;i++){
            int x;
            scanf("%d",&x);
            int S = max(i-K,0);
            if (S==0) S=N+1;
            int T= min(i+K-1,N);
            jt(T,S,-x);
            //cout<<T<<" "<<S<<endl;
        }
        for (int i=2;i<=N;i++){
            jt(i,i-1,0);
        }
        jt(1,N+1,0);
        int Q;
        scanf("%d",&Q);
        while (Q--){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            int S=x-1;
            if (x-1 <=0) S=N+1;
            jt(S,y,z); 
        }
        SPFA(N,N+1);
        if (Loop) printf("-1\n");
        else printf("%d\n",-dis[N+1]);
        Clear(N+5);
    }
    return 0;
} 
复制代码

 1005

 

对顶栈有点妙发现范围很小,可以直接用矩阵存一下方案数转移

然后发现区间的l,r都是单调的

可以双指针

但是有问题,矩阵的逆不一定存在,所以不能除

所以要规避除法,有两个做法

1、线段树直接查区间矩阵乘积,不知道会不会被卡

2、对顶栈

细说一下对顶栈

把一个区间[l,r]拆成[l,lim)[lim,r]两个部分

对于左边那个部分,每个点都存一个后缀的矩阵乘

对于右边那个部分,每次就直接乘上当前位置的矩阵

此时,我们就会发现,左边是一个只会出栈的栈,右边是一个只会入栈的栈。

每次,在左边的栈空掉之后,我们就把右边的栈全部pop,然后压进左边这个栈

通过这种方法,我们就可以直接通过查询左边的栈到栈顶的乘积,以及右边所有元素的乘积来查询[l,r]

然后,其实每个元素只会在右边的栈进出一次,左边一次

所以是线性的。

复制代码
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e9;
int N,M,K;
int limit(long long x){
    if(x>=INF) return INF;
    else return x;
}
struct Matrix{
    long long A[55][55];
    Matrix operator * (Matrix B) const{
        Matrix New;
        for (int i = 1;i<=M;i++)
            for (int j = 1; j <= M; j++)
                New.A[i][j] = 0;
        for (int i = 1 ; i <= M ; i++)
            for (int j = 1 ;j <= M ; j++)
                for (int k = 1 ; k <= M ;k++){
                    New.A[i][j] = limit(New.A[i][j]+(long long)A[i][k]*B.A[k][j]);
            }
        return New;
    }
    void Output(){
        cout<<"qwq"<<endl;
        for (int i = 1;i<=M;i++){
            for (int j = 1;j<=M;j++){
                cout<<A[i][j]<<" ";
            }
            cout<<endl;
        }
        cout<<"qwq"<<endl;
    }
    void setOne(){
        for (int i = 1; i <= M ; i ++)
            for (int j =1 ; j <= M ; j ++)
                A[i][j] = 0;
        for (int i = 1 ; i <= M ; i ++)
            A[i][i]=1;
    }
}d[5005];
bool Check(Matrix a,Matrix b){
    long long ans = 0;
    for (int i = 1;i<= M;i++){
        ans = min(INF,ans + 1ll*a.A[1][i]*1ll*b.A[i][M]);
    }
    //cout<<ans<<endl;
    return ans <= K;
}
int cnt = 0; 
int T,TT;
Matrix stk[5005];
void Solve(){
    scanf("%d%d%d",&N,&M,&K);
    for (int i = 1 ;i<= N ;i ++ ) d[i].setOne();
    for (int i = 1 ;i<=N;i++){
        int l;
        scanf("%d",&l);
        while (l--){
            int u,v;
            scanf("%d%d",&u,&v);
            d[i].A[u][v] = 1;
        }
    }
    Matrix nw;
    nw.setOne();
    int ans = 0 ;
    Matrix Other;
    Other.setOne();
    stk[0].A[1][M] = INF;
    for (int l=0,r=1,lim=0;r<=N;r++){      
        Other = Other * d[r];
        while (!Check(stk[l],Other)){
            l++;
            if (l>lim){
                stk[r] = d[r];
                for (int i = r-1 ; i > lim;i--)
                    stk[i] = d[i] * stk[i+1] ;
                Other.setOne();
                lim = r; 
            }
        }
        ans = max(ans,r-l+1);
    }
    printf("%d\n",ans);
}
int main(){
    scanf("%d",&T);
    TT = T;
    while (T--){
        cnt++;  
        Solve();
    }
    return 0;
}
复制代码

 

7.30牛客

D

前缀min一下就好了

注意读入格式。

复制代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n,q,m;
const ll p=998244353;
ll seed;
inline int gi(){
    int x = 0, f = 1, c = getchar();
    for(; !isdigit(c); c = getchar()) f = c=='-'?-1:1;
    for(; isdigit(c); c = getchar()) x = x*10 + (c^48);
    return x * f;
}
int Min[11][405][405];
ll anss=0;
ll poww(ll a, ll b){
    ll ans = 1, base = a; 
    while (b){
        if (b&1) ans=1ll*ans*base%p;
        base = 1ll*base*base%p;
        b>>=1;
    }
    return ans;
}

ll solve(int IQ,int EQ, int AQ)
{
    ll ans=0;
    //printf("%d %d %d\n", IQ, EQ, AQ);
    //IQ--, EQ--, AQ--;
    for (int i = 1; i <= n; i++) if (Min[i][IQ][EQ] <= AQ) ans++;
    //cout <<"ans="<< ans << endl;
    return ans;
} 

void init()
{
    cin >> n >> q;
    memset(Min, 0x3f, sizeof(Min));
    for(int i=1;i<=n;i++)
    {
        m=gi(); // ==m[i]
        for(int j=1;j<=m;j++)
        {
            int a=gi(),b=gi(),c=gi();
            if (Min[i][a][b] > c) Min[i][a][b] = c;    
        }    
        for (int j = 1; j <= 400; j++) 
            for (int k = 1; k <= 400; k++) {
                if (Min[i][j][k] > Min[i][j - 1][k]) Min[i][j][k] = Min[i][j - 1][k];
                if (Min[i][j][k] > Min[i][j][k - 1]) Min[i][j][k] = Min[i][j][k - 1];    
            }
    }
    cin >> seed;
}

int main()
{
    init();
    std::mt19937 rng(seed);
    std::uniform_int_distribution<> u(1,400);
    ll lastans=0;
    for (int i=1;i<=q;i++)
    {
        int IQ=(u(rng)^lastans)%400+1;  // The IQ of the i-th friend
        int EQ=(u(rng)^lastans)%400+1;  // The EQ of the i-th friend
        int AQ=(u(rng)^lastans)%400+1;  // The AQ of the i-th friend
        // int IQ,EQ,AQ;
        // if(i==1) IQ=92,EQ=108,AQ=303;
        // if(i==2) IQ=116,EQ=36,AQ=265;
        // if(i==3) IQ=255,EQ=132,AQ=185;
        // if(i==4) IQ=360,EQ=219,AQ=272;
        // if(i==5) IQ=8,EQ=115,AQ=254;
        lastans=solve(IQ,EQ,AQ);  // The answer to the i-th friend
        anss=(anss+(ll)lastans*poww(seed,q-i)%p)%p;
    }
    cout << anss << endl;
    return 0;
}
复制代码

H

枚举长宽,暴力check然后暴力输出

填的话就贪心的填就好了。

复制代码
#include <bits/stdc++.h>
using namespace std;
inline int gi(){
    int x = 0, f = 1, c = getchar();
    for(; !isdigit(c); c = getchar()) f = c=='-'?-1:1;
    for(; isdigit(c); c = getchar()) x = x*10 + (c^48);
    return x * f;
}
#define ll long long
int b[110];
void print(int x,int y,int n)
{
    for(int i=1;i<=n;i++) b[i]=n-i+1;
    //cout << " x "<<x<<" y "<<y<<endl;
    for(int i=1;i<=y;i++)
    {
        int t=x;
        while(t){
            for(int j=n;j>=1;j--)
                while(b[j]&&j<=t) {
                    b[j]--;
                    printf("%d %d %d %d\n",x-t,i-1,x-t+j,i);
                    t-=j;
                }
        }
    }    
}

bool ok(int x,int y, int n)
{
    for(int i=1;i<=n;i++) b[i]=n-i+1;

    for(int i=1;i<=y;i++)
    {
        int t=x;
        while(t){
            int flag=0;
            for(int j=n;j>=1;j--)
            {
                //cout<<i<<" "<<t<<" "<<j<<endl;
                while(b[j]&&j<=t) {
                    b[j]--;
                    t-=j;
                    flag=1;
                }
            }
            if(!flag) return false;
        }
    }
    return true;
}

void solve(int n)
{
    int tot=0;
    for(int i=1;i<=n;i++)
        tot+=i*(n-i+1);
    int  ansx=-1,minc=1e9+7;
    for(int x=sqrt(tot);x>=1;x--)
    {
        int y=tot/x;
        if(x*y!=tot) continue;
        int xx=x,yy=y;
        if (xx<yy) swap(xx,yy);
        if(ok(xx,yy,n)) {
            int c=(y+x)*2;
            if(c<minc) {
                //cout<<x<<" "<<y<<" "<<"qwq"<<endl;
                minc=c;
                ansx=max(x,tot/x);            
                break;
            }
        }
    }
    printf("%d\n",minc);
    print(ansx,tot/ansx,n);
}

int main()
{
    int t=gi();
    while(t--)
    {
        int n=gi();
        solve(n);
    }
    return 0;
}
复制代码

K

10kx+m=x+1(modn)

m是一个k1位数

发现k最多到6,因为大于6的时候剩余系被取完了。

所以6n暴力就好了。

记得特判1

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;

int n, MOD;

ll mod(ll a, ll b) {
    return ((a + b) % MOD + MOD) % MOD;
}

ll mylog10(ll a) {
    int res = 0;
    while (a)
        res++, a /= 10;
    return res;
}

void solve() {
    cin >> n;
    if (n==1){
        printf("0\n");
        return;
    }
    MOD = n;
    ll ans = 0;
    for (int i = 1; i <= n; ++i) {
        ll ten = 10;
        for (int k = 1; k <= 7; ++k, ten *= 10) {
            if (mylog10(mod(i, -(i - 1) * ten)) <= k) {
                ans += k;
                break;
            }
        }
    }
    cout << ans << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    solve();
    return 0;
}
复制代码

N

这签到题还挺好玩的(

考虑最后什么情况下不变,也就是每两个数之间都有子集关系

那就把每一位的1全部堆到前缀去就好了。

ll爆了,写int128

复制代码
#include <bits/stdc++.h>
using namespace std;
inline __int128 read() {
    __int128 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*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
inline void print(__int128 x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}
__int128 Sum;
__int128 N,a[100005];
__int128 b[100005],c[100005];
int mx=0;
int main(){
    N=read();
    for (int i=1;i<=N;i++){
        a[i]=read();
        int cnt = 0;
        Sum+=a[i];
        while (a[i]){
            if (a[i] & 1) b[cnt] ++;
            a[i] >>=1;
            cnt++;
            mx=max(mx,cnt);
        }
    }
    for (int i=0;i<=mx;i++){
        for (int j=1;j<=b[i];j++){
            c[j] |= (1ll<<i); 
        }
    }
    __int128 f1=0;
    for (int i=1;i<=N;i++){
        f1 = f1+1ll*(N*c[i]-Sum)*(N*c[i]-Sum);
    }
    //cout<<f1<<endl;
    __int128 f2=1ll*N*1ll*N*1ll*N;
    __int128 G=__gcd(f1,f2);
    f1/=G,f2/=G;
    print(f1);
    cout<<"/";
    print(f2); 
    return 0;
}
复制代码

 赛后补题

A

现场脑子抽了才会想不出来这个题怎么弄

这种又排序又选序列的,可以先考虑一下定序然后再dp取子序列(其实这类题很多x)

考虑对一个序列施加小干扰

考虑两个相邻的数xy

设他们前面的乘积为P

xy前的贡献是

Pwx+Ppxwy(i)

yx前的贡献是

Pwy+Ppywx(ii)

iii

xy前的条件是

wxwy+pxwypywx>0

根据这东西排个序,然后dp

dp的时候为了当前贡献的计算不被前面选择的内容影响,需要倒着dp

每次选择一个数就相当于给后面的答案都乘上pi

dp[i][j]=max(dp[i+1][j],dp[i+1][j1]pi+wi

复制代码
//dp[i][j] = dp[i+1][j-1] * p[i] + w[i]
#include<bits/stdc++.h>
using namespace std;
double dp[100005][21];
struct Node{
    int w,q;
}a[100005];
bool cmp(Node a,Node b){
    return (a.w-b.w+a.q/10000.0*b.w-b.q/10000.0*a.w > 0);
}
int main(){
    int N,M;
    scanf("%d%d",&N,&M);
    for (int i=1;i<=N;i++)
        scanf("%d",&a[i].w);
    for (int i=1;i<=N;i++)
        scanf("%d",&a[i].q);
    //for (int i=1;i<=N;i++)
    //    cout<<a[i].q<<" "<<a[i].w<<endl;
    sort(a+1,a+N+1,cmp);
    for (int i=N;i>=1;i--)
        for (int j=0;j<=M;j++){
            dp[i][j] = dp[i+1][j];
            if (j-1>=0) dp[i][j] = max(dp[i][j],dp[i+1][j-1]*a[i].q/10000.+a[i].w);
        }
    printf("%.10lf",dp[1][M]);
    return 0;
} 
复制代码

 L

一万年见不到一次的题型(

首先,维基百科告诉我们:正凸多边形只有5

https://zh.m.wikipedia.org/zh-hans/%E6%AD%A3%E5%A4%9A%E9%9D%A2%E9%AB%94

然后我们就可以利用我们的立体几何知识对这五种立体图形发生一次坍缩产生的变化进行计算了。

然后就结束了(

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
int main(){
    scanf("%d",&T);
    while (T--){
        int N,a,K;
        scanf("%d%d%d",&N,&a,&K);
        double ansa = a;
        bool flag = false;
        while (K--){
            if (N == 4){
                N=4,ansa=ansa/3;
            }else
            if (N==6){
                N=8,ansa=ansa*sqrt(2)/2;
            }else
            if (N==8){
                N=6,ansa=ansa*sqrt(2)/3;
            }else
            if (N==12){
                N=20,ansa=ansa*(3*sqrt(5)+5)/10;
            }else if (N==20){
                N=12,ansa=ansa*(sqrt(5)+1)/6;
            }
            else{
                printf("impossible\n");
                flag = true;
                break; 
            }
        }
        if (!flag){
            printf("possible %d %.15lf\n",N,ansa);
        }
    }
    return 0;
}
复制代码

 8.1牛客

不做评价,也没有题解。我觉得浪费了五小时(

8.2杭电

1007

首先发现一个点一个入一个出,所以一定是一堆环

每个环上的问题相当于从环上取k个不相邻的数的方案数

组合数学分析一下,答案是Cnk1k1+Cnkk

然后把每个环的生成函数一写,分治ntt求一下xk的系数就好了。

复制代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<iostream>
#define ll long long
using namespace std;
const ll T=30,P=998244353;
const long long fish=998244353;
int N,M;
long long fac[500005],inv[500005];
long long Pow(long long x,long long y){
    long long ans=1;
    for (;y;y>>=1,x=1ll*x*1ll*x%fish)
        if (y&1) ans=1ll*ans*1ll*x%fish;
    return ans;
}
void Pre(){
    fac[0]=1;
    for (int i=1;i<=500000;i++)
        fac[i]=1ll*fac[i-1]*1ll*i%fish;
    inv[500000]=Pow(fac[500000],fish-2);
    for (int i=499999;i>=0;i--){
        inv[i]=1ll*inv[i+1]*1ll*(i+1)%fish;
    }
}
long long C(long long n,long long r){
    if (n<0 || r<0 || n-r<0) return 0;
    return 1ll*fac[n]*1ll*inv[r]%fish*1ll*inv[n-r]%fish;
}
struct Poly{
    ll a[2000010],n;
}F[T];
ll n,m,r[2000010],x[2000010],y[2000010];
bool v[T];
ll power(ll x,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*x%P;
        x=x*x%P;b>>=1;
    }
    return ans;
}
void NTT(ll *f,ll n,ll op){
    for(ll i=0;i<n;i++)
        if(i<r[i])swap(f[i],f[r[i]]);
    for(ll p=2;p<=n;p<<=1){
        ll tmp=power(3,(P-1)/p),len=p>>1;
        if(op==-1)tmp=power(tmp,P-2);
        for(ll k=0;k<n;k+=p){
            ll buf=1;
            for(ll i=k;i<k+len;i++){
                ll tt=buf*f[i+len]%P;
                f[i+len]=(f[i]-tt+P)%P;
                f[i]=(f[i]+tt)%P;
                buf=buf*tmp%P;
            }
        }
    }
    if(op==-1){
        ll invn=power(n,P-2);
        for(ll i=0;i<n;i++)
            f[i]=f[i]*invn%P;
    }
    return;
}
void Mul(Poly &F,Poly &G){
    ll n=1;
    while(n<F.n+G.n)n<<=1;
    for(ll i=0;i<F.n;i++)x[i]=F.a[i];
    for(ll i=0;i<G.n;i++)y[i]=G.a[i];
    for(ll i=F.n;i<n;i++)x[i]=0;
    for(ll i=G.n;i<n;i++)y[i]=0;
    for(ll i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)?(n>>1):0);
    NTT(x,n,1);NTT(y,n,1);
    for(ll i=0;i<n;i++)x[i]=x[i]*y[i]%P;
    NTT(x,n,-1);
    for(ll i=0;i<n;i++)F.a[i]=x[i];
    F.n=F.n+G.n-1;return; 
}
ll Find(){
    for(ll i=0;i<T;i++)
        if(!v[i]){v[i]=1;return i;}
}
int Siz[500010],Size;
bool vis[500010];
ll Solve(ll l,ll r){
    if(l==r){
        ll p=Find();
        F[p].a[0]=1;
        long long NN = Siz[l];
        for(ll i=1;i<=(Siz[l]+3)/2;i++){
        F[p].a[i]=(C(NN-i-1,i-1)+C(NN-i,i))%P;
        F[p].n=(Siz[l]+3)/2+1;
        }
        return p;
    }
    ll mid=(l+r)>>1;
    ll ls=Solve(l,mid),rs=Solve(mid+1,r);
    Mul(F[ls],F[rs]);v[rs]=0;
    return ls;
}
int PP[500010];
signed main()
{
    int T;
    Pre();
    int K;
    scanf("%d",&T);
    while (T--){
    scanf("%d%d",&N,&K);
    Size=0;
    for (int i=1;i<=N;i++){
        scanf("%d",&PP[i]);
        vis[i]=0;
        Siz[i] = 0;
    }
    for (int i=1;i<=N;i++)
        if (!vis[i]){
            Size++;
            int cntt=0;
            int x=i;
            while (!vis[x]){
                cntt++;
                vis[x]=true;
                x=PP[x];
            }
            Siz[Size] = cntt;
    }
        ll p=Solve(1,Size);
        printf("%lld\n",F[p].a[K]);
    }
    return 0;
}
/*
2 3 4 1 6 7 5 9 10 11 8
*/
复制代码

1010

签到,先手显然可以第一次就喊最大的那个数,然后数量1

唯一会输的情况是俩顺子

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
int cnt1[10],cnt2[10];
int x,y;
int main(){
    scanf("%d",&T);
    int N;
    while (T--){
        memset(cnt1,0,sizeof(cnt1));
        memset(cnt2,0,sizeof(cnt2));
        scanf("%d",&N);
        bool flag = false;
        for (int i=1;i<=N;i++){
            scanf("%d",&x);
            cnt1[x]++;
            if (cnt1[x]!=1) flag = true;
        }
        for (int i=1;i<=N;i++){
            scanf("%d",&y);
            cnt2[y]++;
            if (cnt2[y] !=1) flag = true;
        }
        //if (N<6 && cnt1[1] != 0 || cnt2[1] !=0 && !flag) flag = true; 
        if (flag) printf("Win!\n");
        else printf("Just a game of chance.\n");
    }
    return 0;
}
复制代码

1012

考虑模拟这个过程

最开始写了俩堆然后T飞了(

考虑把每个时刻的加入分为两个操作

第一个操作是丢已经走的人

这个开个大根堆,每次取堆顶和当前时刻比较

第二个操作是加入人

这时候需要查询区间内人数最少的数的位置

写个线段树即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
struct Node1{
    long long Time;
    int id;
    bool operator <(const Node1 &v)const{return Time>v.Time;}
};
struct Node2{
    long long a, s;
}a[200100];
struct Node{
    int cnt,id;
}Tree[800100];
Node Min(Node a,Node b){
    if (a.cnt == b.cnt && a.id < b.id) return a;
    if (a.cnt == b.cnt && a.id > b.id) return b;
    if (a.cnt<b.cnt) return a;
    if (a.cnt>b.cnt) return b;
} 
void Add(int Now,int l,int r,int Ned,int val){
    if (l==r){
        Tree[Now].cnt += val;
        return;
    }
    int mid=(l+r)>>1;
    if (Ned <= mid) Add(Now<<1,l,mid,Ned,val);
    else Add(Now<<1|1,mid+1,r,Ned,val);
    Tree[Now] = Min(Tree[Now<<1],Tree[Now<<1|1]);
}
void Build(int Now,int l,int r){
    if (l==r){
        Tree[Now].cnt=0;
        Tree[Now].id=l;
        return;
    }
    int mid=(l+r)>>1;
    Build(Now<<1,l,mid);Build(Now<<1|1,mid+1,r);
    Tree[Now] = Min(Tree[Now<<1],Tree[Now<<1|1]);
}
bool temp(Node2 a,Node2 b){
    return a.a<b.a; 
}
long long ls[200100];
priority_queue<Node1> In;
int T;
int main(){
    scanf("%d",&T);
    while (T--){
        int N,M;
        scanf("%d%d",&N,&M);
        for (int i=1;i<=N;i++){
            scanf("%lld%lld",&a[i].a,&a[i].s);
        }
        sort(a+1,a+N+1,temp);
        for (int i=1;i<=M;i++) ls[i] = 0;
        Build(1,1,M);
        for (int pos=1;pos<=N;pos++){
            while (!In.empty()){
                Node1 Now = In.top();
                if (Now.Time > a[pos].a) break;
                In.pop();
                Add(1,1,M,Now.id,-1);
            }
            Node Que = Tree[1];
            ls[Que.id] = max(ls[Que.id],a[pos].a) + a[pos].s;
            Add(1,1,M,Que.id,1);
            In.push({ls[Que.id],Que.id});
        }
        long long ans=0;
        for (int i=1;i<=M;i++)
            ans=max(ans,ls[i]);
        printf("%lld\n",ans);
    }
    return 0;
}
复制代码

 补题

1011

一眼就感觉在哪见过(

300iqcontestKKnowledge

但是当时不想手玩了,群论做法又每太弄明白,于是开摆了(

做法的话就是知道结论,这样操作的话,每个字符串的最小表示法一定只有24种,人工跑出来比较就好了。

群论不会,会了再补(

复制代码
#include <bits/stdc++.h>
using namespace std;
string ss1,ss2;
map<string,string> mp;
int main(){
    mp["aa"] = ""; mp["bc"] = "a"; mp["ca"] = "bb"; mp["abc"] = "";
    mp["aca"] = "abb"; mp["baa"] = "b"; mp["bab"] = "acc";
    mp["bba"] = "c"; mp["bbb"] = ""; mp["bbc"] = "ba";
    mp["cbc"] = "bb"; mp["cca"] = "cbb"; mp["ccc"] = "ab";
    mp["abaa"] = "ab"; mp["abab"] = "cc"; mp["abba"] = "ac";
    mp["abbb"] = "a"; mp["abbc"] = "aba"; mp["acbc"] = "abb";
    mp["acca"] = "acbb"; mp["accc"] = "b"; mp["baca"] = "accb";
    mp["bacc"] = "cb"; mp["cbaa"] = "cb"; mp["cbab"] = "bac";
    mp["cbba"] = "cc"; mp["cbbb"] = "c"; mp["cbbc"] = "cba";
    mp["ccba"] = "abac"; mp["ccbb"] = "aba"; mp["ccbc"] = "cbb";
    mp["abaca"] = "ccb"; mp["abacb"] = "cbac"; mp["abacc"] = "acb";
    mp["acbaa"] = "acb"; mp["acbab"] = "abac"; mp["acbac"] = "bacb";
    mp["acbba"] = "acc"; mp["acbbb"] = "ac"; mp["acbbc"] = "acba";
    mp["accba"] = "bac"; mp["accbb"] = "ba"; mp["accbc"] = "acbb";
    mp["bacba"] = "cbac"; mp["bacbb"] = "cba"; mp["bacbc"] = "accb";
    mp["cbaca"] = "bacb"; mp["cbacb"] = "acba"; mp["cbacc"] = "ccb";
    int T;
    scanf("%d",&T);
    while (T--){
        string s1,s2;
        cin>>s1;
        cin>>s2;
        int Len1=s1.length();
        int Len2=s2.length();
        string s3="",s4="";
        for (int i=0;i<Len1;i++){
            s3=s3+s1[i];
            if (mp.count(s3)) s3=mp[s3];
        }
        for (int i=0;i<Len2;i++){
            s4=s4+s2[i];
            if (mp.count(s4)) s4=mp[s4];
        }
        if (s3 == s4) printf("yes\n");
        else printf("no\n");
    } 
}
复制代码

 1004


首先要读懂题意

范围很小

暴力状压一下每个控制点能看到的点,用int128或者bitset

控制点能看到等价于两点之间连的直线不经过任何一条矩形的线段

因为范围很小,暴力跑即可。

至于判断方法,就直接判断线段的两端点是否在直线两侧即可,叉积一下。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,M;
struct Node{
    long long x,y;
    void input(){
        scanf("%lld%lld",&x,&y);
    }
    Node(int X=0,int Y=0){
        x=X,y=Y;
    }
    Node operator - (Node A) const{
        return Node(x-A.x,y-A.y);
    }
    long long operator * (Node A) const{
        return (x*A.y-A.x*y);
    }
    bool operator == (Node A) const{
        return (x == A.x && y == A.y);
    }
}q[155][5],p[155];
int sgn(long long x){
    if (x==0) return 0;
    return (x>0?1:-1);
}
bool Cross(Node A,Node B,Node C,Node D){
    return (sgn((B-A)*(C-A))*sgn((B-A)*(D-A))) <= 0;//判断线段与直线相交 
}
bool Check(Node A,Node B){
    for (int i=0 ; i<M ; i++){
        for (int j = 0 ;j <= 3 ; j++){
            if ((B == q[i][j]) || (B == q[i][(j+1)%4])) continue;//判断点重复 
            if (Cross(A,B,q[i][j],q[i][(j+1)%4]) && Cross(q[i][j],q[i][(j+1)%4],A,B)) return 0;
        }
    }
    return 1; 
}
void output(__int128 x)
{
    if (!x) return ;
    if (x < 0) putchar('-'),x = -x;
    output(x / 10);
    putchar(x % 10 + '0');
}
__int128 S[155][5],u=1;
long long T[55];
void Solve(){
    memset(S,0,sizeof(S));
    memset(T,0,sizeof(T));
    scanf("%d%d",&N,&M);
    for (int i=0 ; i < N;i++){
        p[i].input();
    }
    for (int i = 0 ; i < M; i++)
        for (int j = 0 ; j <= 3 ; j++)
            q[i][j].input();
    for (int i = 0 ; i < N ; i++ )
        for (int j=0 ; j < N ; j ++){
            if (i==j) continue;
            if (Check(p[i],p[j])){
                T[i] |= 1<<j;
            }
        }
    for (int i = 0 ; i< N ;i++)
        for (int j = 0 ; j < M ; j++)
            for (int k = 0 ;k < 4 ; k++)
                if (Check(p[i],q[j][k])){
                    S[i][k] |= (u<<j);
        }
    int ans = 1e9; 
    long long MS = ((1<<N)-1);
    __int128 SS = ((u<<M)-1);
    for (int ST=0;ST<=MS;ST++){
        __int128 a1=0,a2=0,a3=0,a4=0;
        int t=0,cnt = 0;
        for (int i = 0 ; i < N ; i++){
            if (ST>>i&1){
                a1 |= S[i][0];
                a2 |= S[i][1];
                a3 |= S[i][2];
                a4 |= S[i][3];
                t  |= T[i];
                cnt ++;
            }
        }
        if (a1 == SS && a2 == SS && a3 == SS && a4 == SS && (t&ST) == ST){
            ans = min(ans,cnt);
        }
    }
    if (ans == 1e9){
        cout<<"No Solution!\n";
    }
    else cout<<ans<<endl;
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        Solve();
    }
}
复制代码

 1002(未完成)

写个想法(
考虑把期望那个式子拆一拆,因为一个数的因子要和原本的数字相同

考虑一个数质因数分解后的结果,会发现其实这个东西是ki,其中ki是当前数质因子分解后每个指数的大小

所以这个数的贡献就是:1nxki

其实1n可以单独拿出去乘

所以把后面的东西的和设为f(x)

考虑它在f(p)f(pk)处的函数值

f(p)=p

f(pk)=xk

而且显然这函数是积性的

用亚线性筛法筛出前缀和应该就可以了。

题解好像用的pn筛,回头用min25筛试试能不能筛出来(

8.4杭电

七夕节?单刷节!

1006


树形dp,考虑一下每个点的mex最大怎么算就好了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N;
vector<int> G[500005];
long long dp[500005],Siz[500005];
void dfs(int Now,int fa){
    Siz[Now] = 1;
    for (auto v:G[Now]){
        if (v == fa) continue;
        dfs(v,Now);
        dp[Now] = max(dp[Now],dp[v]);
        Siz[Now] += Siz[v]; 
    }
    dp[Now] += Siz[Now];
}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            G[i].clear();
            dp[i]=0;
            Siz[i]=0;
        }
        for (int i=1;i<N;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dfs(1,1);
        printf("%lld\n",dp[1]);
    }
    return 0;
} 
复制代码

1007

计算aks=x的方案

两边把s的逆元一乘

ak=xs1

然后map记录一下每个余数的最小k

每次询问扫一遍n个,每次判断一下步数和最小k的关系

复制代码
#include <bits/stdc++.h>
using namespace std;
long long s[5005],d[5005];
unordered_map<int,int> Ti;
long long Pow(long long x,long long y,long long P){
    long long ans=1;
    for (;y;){
        if (y&1) ans=ans*x%P;
        x=1ll*x*1ll*x%P;
        y>>=1;
    }
    return ans;
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        long long P,a,N,q;
        Ti.clear();
        scanf("%lld%lld%lld%lld",&P,&a,&N,&q);
        for (int i=1;i<=N;i++){
            scanf("%lld%lld",&s[i],&d[i]);
            s[i] = Pow(s[i],P-2,P);
        }
        long long nw=1;
        for (int i=0;i<=200000;i++){            
            if (!Ti.count(nw)) Ti[nw] = i;
            nw = nw*a%P;
        }
        while (q--){
            int x;
            scanf("%d",&x);
            int ans=0;
            for (int i=1;i<=N;i++){
                long long nww = 1ll*x*s[i]%P;
                if (x==0 && s[i] == 0 || Ti.count(nww) && Ti[nww] <= d[i]) ans++;
                //if () ans++;
            }
            printf("%d\n",ans);
        }
    }
}
复制代码

1010

yy一下发现破成一个森林就好了

要求字典序最小,跑一边krusual

 

复制代码
#include <bits/stdc++.h>
using namespace std;
int fa[100005];
int Getfa(int x){
    return ((x==fa[x])?x:fa[x]=Getfa(fa[x]));
}
int u[400005],v[400005];
int cnt=0;
int id[400005];
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        int N,M;
        cnt=0;
        scanf("%d%d",&N,&M);
        for (int i=1;i<=N;i++)
            fa[i]=i;
        for (int i=1;i<=M;i++){
            scanf("%d%d",&u[i],&v[i]);
        }
        for (int i=M;i>=1;i--){
            int fx=Getfa(u[i]),fy=Getfa(v[i]);
            if (fx == fy) id[++cnt]=i;
            else fa[fy] = fx;
        }
        printf("%d\n",cnt);
        for (int i=cnt;i>=1;i--)
            printf("%d ",id[i]);
        printf("\n"); 
    }
    return 0;
}
复制代码

1009

赛场上想的是坐标变换解矩阵,然后赛后发现解一个向量系数就好了(

代码赛场上来不及写了,回头补吧(

8.8牛客

C

队友写的,好像是个二分图匹配quq

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int N = 5e5 + 5;
const int INF = 0x3f3f3f3f;

int n, f[N], ans[N];
vector<pair<int,int>> v;

void solve() {
    cin >> n;
    v.clear();
    for (int i = 1; i <= n; ++i)
        f[i] = ans[i] = 0;
    for (int i = 1; i <= n; ++i) {
        int a; cin >> a;
        if (!f[a]) {
            f[a] = true;
            v.push_back(make_pair(a, i));
        }
    }
    if (v.size() == 1) {
        cout << "NO" << endl;
        return;
    }
    cout << "YES" << endl;
    for (int i = 0; i < v.size() - 1; ++i)
        ans[v[i].second] = v[i + 1].first;
    ans[v[v.size() - 1].second] = v[0].first;
    int p = 1;
    while (p <= n && f[p])
        p++;
    for (int i = 1; i <= n; ++i) {
        if (ans[i]) {
            cout << ans[i] << " ";
            continue;
        }
        cout << p++ << " ";
        while (p <= n && f[p])
            p++;
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T; cin >> T;
    while (T--)
        solve();
    return 0;
}
复制代码

F

发现两种操作之间互相自己不影响

所以其实怎么删都无所谓

能删就删就好了。

拿链表模拟这个过程

记得特判一下1

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int N = 1e5 + 5;
const int INF = 0x3f3f3f3f;

int n, nex[N], pre[N], t[N], f[N], m;

void solve() {
    cin >> n >> m;
    if (n <= 1) {
        cout << 0 << endl;
        return;
    }
    for (int i = 1; i <= n; ++i) {
        cin >> t[i];
        if (i < n)
            nex[i] = i + 1;
        if (i > 1)
            pre[i] = i - 1;
    }
    nex[n] = 1;
    pre[1] = n;
    int ans = 0;
    for (int i = 1; !f[i]; i = nex[i]) {
        f[i] = 1;
        bool is = true;
        while (is) {
            is = false;
            if (t[pre[i]] == t[i] || t[pre[i]] + t[i] == m) {
                ans++;
                is = true;
                nex[pre[pre[i]]] = nex[i];
                pre[nex[i]] = pre[pre[i]];
                i = nex[i];
                f[i] = 1;
            }
            if (n - ans * 2 <= 1)
                break;
            if (t[nex[i]] == t[i] || t[nex[i]] + t[i] == m) {
                ans++;
                is = true;
                nex[pre[i]] = nex[nex[i]];
                pre[nex[nex[i]]] = pre[i];
                i = pre[i];
                f[i] = 1;
            }
            if (n - ans * 2 <= 1)
                break;
        }
    }
    cout << ans << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    solve();
    return 0;
}
复制代码

G

我佛了啊,什么阅读理解题(

首先搞清楚正则表达式怎么写

然后就会发现,其实..+可以表示所有字符串

那么答案长度就是2

但是长度为2

ab为例

还会有

ab

.a

a.

..

这四种

所以样例是6

但是所有元素都相同的话

比如aaaa

会有a+

a

所以答案多2

根据这个if else一下就好了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
string ss;
int main(){
    scanf("%d",&T);
    while (T--){
        cin>>ss;
        if (ss.length() == 1){
            printf("1 2\n");
            continue;
        }
        int Len = ss.length();
        bool flag=false;
        for (int i=1;i<Len;i++)
            if (ss[i] != ss[i-1]) flag = true;
        if (Len == 2){
            if (!flag)
                printf("2 8\n");
            else printf("2 6\n");
        }
        else{
            if (!flag)
                printf("2 4\n");
            else printf("2 2\n");
        }
    }
    return 0;
} 
复制代码

J

发现考虑原序列很难

其实我们发现,一个前缀和序列一定能还原一个原序列

然后就考虑前缀和序列(模k意义下)

前缀和序列,有x个相等的时候会贡献CX2个和为0的组

而这些位置在哪其实并不影响答案

问题就转化成,有k种前缀和,塞入i个位置,搞出j组满足条件的方案数

背包一下就好了。

dp[i][j]表示用了i个位置,j组满足条件的方案

然后转移的时候枚举一下当前放的前缀和是多少,满足条件的组数,有几个位置,以及当前的前缀和放几个位置就好了。

想到前缀和之后一下子就通了(

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=70,P=998244353;
int n,k,t,C[N][N],f[N][N*N];
int Pre[N];
int main(){
    scanf("%d%d%d",&n,&k,&t);
    C[0][0]=1;
    for (int i=1;i<=n;i++) Pre[i+1] = Pre[i]+i;
    for (int i=1;i<=n;++i){
        C[i][0]=C[i][i]=1;
        for (int j=1; j<i;++j) C[i][j] = (C[i-1][j-1]+C[i-1][j])%P;
    }
    for (int i=0;i<=n&&Pre[i+1]<=t;++i) f[i][Pre[i+1]]=C[n][i];
    for (int i=1;i<k;++i)
        for (int j=n;j;j--)
            for (int l=1;l<=j &&Pre[l]<=t;l++)
                for (int r=0;r<=t-Pre[l];r++)
                    if (f[j-l][r]) f[j][r+Pre[l]]=(1ll*f[j-l][r]*C[n-j+l][l]+f[j][r+Pre[l]])%P;
    cout<<f[n][t];
    return 0;
}
复制代码

 补题

K

怎么会有人不会莫队啊

下面感性的证几个结论(

首先,只有1堆的情况先手是必胜的

这个情况显然

那么,有2堆的情况呢?

首先,显然的,两个人都不会主动去使用2操作

因为这会使对方获得一个先手只有一堆的情况,而刚才说过了,这种情况先手必胜。

那么就只有1操作

这就是个经典的NIM游戏

继续推广,到N=3的情况

我们发现,先手一定能通过从最大的那堆里面取一些石头,放到最小的那堆里,使得剩下的两堆相等

而这根据上面的分析,这种情况下先手是必败的(经典的NIM游戏的结论)

那么,之后就是类似的操作了,可以归纳证明

也就是说,区间长度为奇数的情况下,先手是必胜的

区间长度为偶数的情况下,是个NIM游戏

于是我们就考虑,计算区间长度为偶数的,且NIM游戏必败的区间

也就是对每个询问,问区间内有多少个长度为偶数且xor和为0的子区间。

发现可以离线询问

然后我就不会了

 

然后显然每次增删一个数字的时间是O(1)

跑个莫队就好了。

复制代码
#include<bits/stdc++.h>
using namespace std;
int blo;
#define bel(x) (x-1)/blo + 1
struct Node{
    int l,r,id;
}a[100005];
long long Ans[100005],ans;
int Sum[100005];
long long cnt[2][2000005];
bool temp(Node a,Node b){
    if (bel(a.l) != bel(b.l)) return (a.l<b.l);
    else return (bel(a.l)&1)?(a.r<b.r):(a.r>b.r);
}
void Add(int x){
    ans -= cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2;
    cnt[x&1][Sum[x]] ++;
    ans += cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2;
}
void Del(int x){
    ans -= cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2;
    cnt[x&1][Sum[x]] --;
    ans += cnt[x&1][Sum[x]]*(cnt[x&1][Sum[x]]-1)/2;
}
int main(){
    int N,Q;
    scanf("%d%d",&N,&Q);
    for (int i=1;i<=N;i++){
        scanf("%d",&Sum[i]);
        Sum[i]--; 
        Sum[i]^=Sum[i-1];
    }
    for (int i=1;i<=Q;i++){
        scanf("%d%d",&a[i].l,&a[i].r);
        a[i].l--;
        a[i].id=i;
    }
    blo = sqrt(N);
    sort(a+1,a+Q+1,temp);
    int l=1,r=0;
    for (int i=1;i<=Q;i++){
        while (l > a[i].l) Add(--l);
        while (r < a[i].r) Add(++r);
        while (l < a[i].l) Del(l++);
        while (r > a[i].r) Del(r--);
        long long Len = r-l;
        Ans[a[i].id] = 1ll*Len*1ll*(Len+1)/2 - ans;
    }
    for (int i=1;i<=Q;i++)
        printf("%d\n",Ans[i]);
    return 0;
} 
复制代码

E

有趣的数据结构(

首先先离散化。

考虑一个数字,一定只有两种可能:一种放在峰值左边,一种放在峰值右边。

而我们贪心一下,优先移动步数少的

然后可以发现的是,根据冒泡排序,一个数的移动次数和它的逆序对数量有关。

而我们就可以拉两个树状数组,分别维护在峰值左边和在峰值右边的数字是什么

然后用树状数组求逆序对

然后我们可以发现一件事情:一个数移动到峰值左边的时候,它的步数是不会变的,而它移到峰值右边的时候,步数一定是不降的。

然后就会发现,这个事情是这样的:

对于一个移动到右边的数来说,加入一个比它小的数,会导致它的逆序对数量+1,也就是移动到R端的代价+1

然后会发现,因为每个数的L的步数都不一样,直接维护L值和R值不太好维护

发现我们只需要知道它们的大小关系,也就是说只要维护LR就可以了(感觉在很多dp里也很经常使用这个手段)

线段树维护区间LR的最小值

然后考虑每次加一个新的值的影响:

1、修改TLTR树状数组里的值

2、修改seg的值,R+1意味着LR的值(因为L不变)

3、利用线段树找到所有在这轮中LR<0的点,暴力把他们从TR树状数组中删除

统计答案即可。

至于峰是最高还是最低,只要队数字做一下反转再跑一遍就好了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
vector<int> mp;
const int mx = 2e5+5;
struct Seg{
    int Tree[mx<<2],Tag[mx<<2];
    void Build(int Now,int l,int r){
        if (l == r){
            Tree[Now] = 1e9;
            Tag[Now] = 0;
            return;
        }
        int mid = (l+r)>>1;
        Build(Now<<1,l,mid);
        Build(Now<<1|1,mid+1,r);
        Tag[Now] = 0;
        Tree[Now] = 1e9;
    }
    void PushDown(int Now){
        if (Tag[Now]){
        Tag[Now<<1] += Tag[Now];
        Tag[Now<<1|1] += Tag[Now];
        Tree[Now<<1] += Tag[Now];
        Tree[Now<<1|1] += Tag[Now];
        Tree[Now] = min(Tree[Now<<1],Tree[Now<<1|1]);
        Tag[Now] = 0;
        }
    }
    void Get(int Now,int l,int r){
        if (Tree[Now]) return;
        if (l==r){
            mp.push_back(l);
            return;
        }
        PushDown(Now);
        int mid = (l+r)>>1;
        Get(Now<<1,l,mid),Get(Now<<1|1,mid+1,r);
    }
    void Modify(int Now,int l,int r,int Pos,int Nd){
        if (l==r){
            Tree[Now] = Nd;
            return;
        }
        PushDown(Now);
        int mid=(l+r)>>1;
        if (Pos<=mid) Modify(Now<<1,l,mid,Pos,Nd);
        else Modify(Now<<1|1,mid+1,r,Pos,Nd);
        Tree[Now] = min(Tree[Now<<1],Tree[Now<<1|1]);
    }
    void Add(int Now,int l,int r,int L,int R,int Nd){
        if (L<=l&&r<=R){
            Tree[Now] += Nd; 
            Tag[Now] += Nd;
            return;
        }
        PushDown(Now);
        int mid = (l+r)>>1;
        if (L <= mid) Add(Now<<1,l,mid,L,R,Nd);
        if (mid <R) Add(Now<<1|1,mid+1,r,L,R,Nd);
        Tree[Now] = min(Tree[Now<<1],Tree[Now<<1|1]);
    }
}seg;
struct BIT{
    int Tree[mx];
    void Clear(){memset(Tree,0,sizeof(Tree));}
    int LowBit(int x){return (x&(-x));}
    void Modify(int Pos,int nd){for (int i=Pos;i<=mx;i+=LowBit(i)) Tree[i] += nd;}
    int Get(int Pos){int ans = 0; for (int i = Pos; i ;i-=LowBit(i)) ans += Tree[i]; return ans;}
}TL,TR;
int a[mx],b[mx];
long long Ans[mx];
void init(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        scanf("%d",&a[i]);
        b[i] = a[i];
    }
    sort(b+1,b+N+1);
    for (int i=1;i<=N;i++){
        a[i] = lower_bound(b+1,b+N+1,a[i])-b;
    }
}
void Solve(){
    long long ans = 0;
    for (int i=1;i<=N;i++){
        //cout<<TR.Get(1)<<" "<<TR.Get(3)<<endl;
        ans = ans + TR.Get(N)-TR.Get(a[i]);
        TL.Modify(a[i],1),TR.Modify(a[i],1);
        seg.Add(1,1,N,a[i],N,-1);        
        seg.Modify(1,1,N,a[i],TL.Get(a[i]-1));        
        mp.clear();
        seg.Get(1,1,N);
        for (auto nw:mp){
            //cout<<i<<" "<<nw<<endl;
            TR.Modify(nw,-1);
            seg.Modify(1,1,N,nw,1e9);
        }
        Ans[i] = min (Ans[i] , ans);
    }
}
void Work(){
    memset(Ans,63,sizeof(Ans));
    TL.Clear();
    TR.Clear();
    seg.Build(1,1,N);
    Solve();
    for (int i=1;i<=N;i++){
        a[i] = N-a[i]+1;
    }
    TL.Clear();
    TR.Clear();
    seg.Build(1,1,N);
    Solve();
    for (int i=1;i<=N;i++)
        cout<<Ans[i]<<'\n'; 
}
int main(){
    init();
    Work();
    return 0;
}
/*
3
3 1 2
*/
复制代码

 

 8.9杭电

1003

关于我完全不知道题解在写什么东西这件事

下面是自己赛场上的做法

首先需要注意的,题目的树是无根树,当成图跑就行了,条件是没有环。

考虑一个火柴人,显然它的头部是比较关键的

我们就先枚举一下头部下面的那个中心点(对于样例来说是枚举点3)

接下来,显然2点只要不和选出来的构成身子和手的点重复就行了

方案数是du3

然后再考虑怎么不重不漏的算三个点

我们对于每个3这种点,遍历它的所有出边

记录前缀的四个数据

1、前缀中一只手的数量

2、一只脚的数量

3、两只手的数量

4、一只手一只脚的数量

那么算答案的时候,只要考虑当前遍历到的这个点的出度提供的是手还是脚,然后更新这四个数值就好了。

因为是前缀,所以不会有选择同一个点同时作为两个部件的情况,而且不重不漏

具体四个数值之间的转移可以看看代码(

 

复制代码
#include <bits/stdc++.h>
using namespace std;
const int fish = 998244353;
int ans = 0;
vector<int> E[500005];
int d[500005];
void calc(int x){
    int H=0,HL=0,L=0,HH=0;
    for (auto v:E[x]){
        int u=x;
        int Hd=d[v]-1;
        int Lg=1ll*(d[v]-1)*(d[v]-2)/2%fish;
        ans = (ans + 1ll*Lg*HH%fish*(d[u]-3)%fish)%fish;
        ans = (ans + 1ll*Hd*HL%fish*(d[u]-3)%fish)%fish;
        HH = (HH + 1ll*Hd*H%fish)%fish;
        HL = (HL + 1ll*Lg*H%fish)%fish;
        HL = (HL + 1ll*Hd*L%fish)%fish;
        (H+=Hd)%=fish;(L+=Lg)%=fish;
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        ans = 0;
        int N;
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            E[i].clear();
            d[i] = 0;
        }
        for (int i=1;i<N;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            E[u].push_back(v);
            E[v].push_back(u);
            d[u]++,d[v]++;
        }
        for (int i=1;i<=N;i++)
            calc(i);
        printf("%d\n",ans);
    }
}
复制代码

 

 04

队友写的。

大概就是能配就配,大力分类讨论

注意黑色的自己能和自己配

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;

int e, l, r, b;

int Min() {
    int res = e;
    if (!l && !r)
        res += min(b, 1);
    res += max(l, r);
    return res;
}

int Max() {
    int res = 0;
    res += l + r;
    res += e + min(e + 1, b);
    return res;
}

void solve() {
    cin >> e >> l >> r >> b;
    cout << Min() << " " << Max() << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T; cin >> T;
    while (T--)
        solve();
    return 0;
}
复制代码

1006

其实是个数位dp板子……

dp[i][j][0/1][0/1]表示的是i位,jd,是否分出大小,前导零。

但是这题有一点比较特殊

如果按照常规的数位dp写的话,需要枚举这一位填了什么

但是B太大,直接枚举会T

考虑一件事,其实转移的时候

只有填入0d是比较特殊的两个转移

所以转移的时候把这两个单独拿出来,其他的跑一遍然后统一乘系数就行了。

是个细节特别多的数位dp

复制代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll Mi;
const ll fish = 1e9+7;
ll dp[75][75];
bool vis[75][75];
int d;
ll B,l,r;
ll a[105];
int cnt;
ll Pow(ll x,ll y){
    ll ans=1;
    if (x==0 && y==0) return 0;
    for (;y;y>>=1){
        if (y&1) ans = ans * x%fish;
        x=x*x%fish;
    }
    return ans;
}
void Get(ll x,ll K){
    cnt = 0;
    while (x){
        a[cnt++] = x%K;
        x/=K;
    }
}
ll dfs(int k,bool flag,bool Z,int DT){
    if (k<0) return Pow(DT,Mi);
    if (!flag && !Z && vis[k][DT]) return dp[k][DT];
    if (!flag && !Z) vis[k][DT] = true;
    ll ans = 0;
    if (flag){
        if (a[k] == d){
            (ans+=dfs(k-1,flag,0,DT+1))%=fish;
            (ans+=dfs(k-1,0,0,DT)*1ll*(d-1)%fish)%=fish;
            (ans+=dfs(k-1,0,Z,DT))%=fish;
        }
        if (a[k] < d){
            (ans += dfs(k-1,0,0,DT)*1ll*(a[k]-1)%fish)%=fish;
            (ans += dfs(k-1,1,0,DT))%=fish;
            (ans += dfs(k-1,0,Z,DT))%=fish;
        }
        if (a[k] > d){
            (ans+=dfs(k-1,1,0,DT))%=fish;
            if (d == 0){
                if(!Z) (ans+=dfs(k-1,0,Z,DT+1))%=fish;
                else (ans+=dfs(k-1,0,Z,DT))%=fish;
                (ans+=dfs(k-1,0,0,DT)*1ll*(a[k]-1)%fish)%=fish;
            }
            else{
                (ans+=dfs(k-1,0,0,DT+1))%=fish;
                (ans+=dfs(k-1,0,Z,DT))%=fish;
                (ans+=dfs(k-1,0,0,DT)*1ll*(a[k]-2)%fish)%=fish; 
            }
        }
    }
    if (!flag){
        if (d==0){
             (ans+=dfs(k-1,flag,0,DT)*1ll*(B-1)%fish)%=fish;
             if (Z) (ans += dfs(k-1,flag,Z,DT))%=fish;
             if (!Z) (ans += dfs(k-1,flag,Z,DT+1))%=fish;
        }
        else{
            (ans +=dfs(k-1,flag,Z,DT))%=fish;
            (ans +=dfs(k-1,flag,0,DT+1))%=fish;
            (ans +=dfs(k-1,flag,0,DT)*1ll*(B-2)%fish)%=fish;    
        }
    }
    if (!flag && !Z) dp[k][DT]=ans;
    return ans;
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        scanf("%lld%lld%d%lld%lld",&Mi,&B,&d,&l,&r);
        Get(r,B);
        memset(vis,0,sizeof(vis));
        memset(dp,0,sizeof(dp));
        ll ans = dfs(cnt-1,1,1,0);
        if (l>1){
            memset(vis,0,sizeof(vis));
            memset(dp,0,sizeof(dp));
            Get(l-1,B);
            ans -= dfs(cnt-1,1,1,0);
            (ans+=fish)%=fish;
        }
        printf("%lld\n",ans);
    }
    return 0; 
}
复制代码

1008

猜结论

感性猜想每一步任取都有办法让三个数能构成三角形

把三个数视为石头堆,因为要维持堆数不变

所以每一个石头堆先1

然后就是NIM游戏

(a1)xor(b1)xor(c1)

有点像昨天的一个题

复制代码
#include <bits/stdc++.h>
using namespace std;
int a,b,c;
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        scanf("%d%d%d",&a,&b,&c);
        a--,b--,c--;
        int x=a^b^c;
        if (x==0) printf("Lose\n");
        else printf("Win\n");
    }
    return 0;
}
复制代码

 8.11杭电

坐牢(

1001

队友写的,看起来像个分类讨论

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;

string s;

void solve() {
    cin >> s;
    int cnt00, cnt01, cnt10, cnt11;
    cnt00 = cnt01 = cnt10 = cnt11 = 0;
    for (int i = 0; i < s.size(); ++i)
        s[i] -= '0';
    for (int i = 0; i < s.size(); i += 2) {
        if (s[i] == 0 && s[i + 1] == 1)
            cnt01++;
        else if (s[i] == 0 && s[i + 1] == 0)
            cnt00++;
        else if (s[i] == 1 && s[i + 1] == 0)
            cnt10++;
        else
            cnt11++;
    }
    if (s.size() & 1) {
        if (s.back() == 0)
            swap(cnt01, cnt10);
        int k = min(cnt01, cnt10);
        cnt11 += k;
        cnt00 += k;
        cnt01 -= k;
        cnt10 -= k;
        if (s.back() == 0)
            cout << 0;
        for (int i = 1; i <= cnt00; ++i)
            cout << "00";
        for (int i = 1; i <= cnt01; ++i)
            cout << "01";
        for (int i = 1; i <= cnt10; ++i)
            cout << "10";
        for (int i = 1; i <= cnt11; ++i)
            cout << "11";
        if (s.back() == 1)
            cout << 1;
        cout << endl;
        return;
    }
    int k = min(cnt01, cnt10);
    cnt11 += k;
    cnt00 += k;
    cnt01 -= k;
    cnt10 -= k;
    for (int i = 1; i <= cnt00; ++i)
        cout << "00";
    for (int i = 1; i <= cnt01; ++i)
        cout << "01";
    for (int i = 1; i <= cnt10; ++i)
        cout << "10";
    for (int i = 1; i <= cnt11; ++i)
        cout << "11";
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T; cin >> T;
    while (T--)
        solve();
    return 0;
}
复制代码

1004

签到,结论是2n

复制代码
#include <bits/stdc++.h>
using namespace std;


int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        int n;
        cin >> n;
        cout <<2*n <<endl;
    }
    return 0;
}
复制代码

1008

树形dp,有一点像最大匹配,但是不完全一样

dp[i][0/1/2]表示到i点,i点选/不选,选了是否已经匹配的最大取点

然后直接转移即可。

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int N = 500000 + 5;
const int INF = 0x3f3f3f3f;

int n, dp[N][3]; // 0 - not selected 1 - selected 2 - selected and compared
int hed[N], nex[N * 2], e[N * 2], ct;

void dfs(int x, int fa) {
    dp[x][0] = dp[x][2] = 0;
    dp[x][1] = 1;
    int sum = 0;
    for (int i = hed[x]; i; i = nex[i]) {
        if (e[i] == fa)
            continue;
        dfs(e[i], x);
        sum += dp[e[i]][0];
    }
    for (int i = hed[x]; i; i = nex[i]) {
        if (e[i] == fa)
            continue;
        dp[x][0] += max({dp[e[i]][0], dp[e[i]][1], dp[e[i]][2]});
        dp[x][1] += dp[e[i]][0];
        dp[x][2] = max(dp[x][2], sum - dp[e[i]][0] + dp[e[i]][1] + 1);
    }
}

void solve() {
    cin >> n;
    for (int i = 1; i <= n; ++i)
        hed[i] = 0;
    ct = 0;
    for (int i = 1; i < n; ++i) {
        int a, b;
        cin >> a >> b;
        e[++ct] = b, nex[ct] = hed[a], hed[a] = ct;
        e[++ct] = a, nex[ct] = hed[b], hed[b] = ct;
    }
    dfs(1, 1);
    // for (int i = 1; i <= n; ++i)
    //     cout << dp[i][0] << " " << dp[i][1] << " " << dp[i][2] << endl;
    cout << max({dp[1][0], dp[1][1], dp[1][2]}) << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int size(512<<20);  // 512M
    __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
    int t;
    cin >> t;
    while (t--)
        solve();
    exit(0);
    return 0;
}
复制代码

1011

题目很长,但其实冷静分析一下就会发现问题是把长和宽分别划分,划分完之后另一边的最小长度是知道的

枚举一边然后直接算即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        ll N,M,K,ans=0;
        scanf("%lld%lld%lld",&N,&M,&K);
        /*for (int i=1;i<=sqrt(K);i++){
            if (K%i == 0){
                ll a=i,b=K/i;
                ll k1=(N-a)/a,k2=(M-b)/b;
                if (k1>=0&&k2>=0) ans=max((k1+1)*(k2+1),ans);
                k1=(M-a)/a.k2=(N-b)/b;
                if (k1>=0 && k2>=0) ans=max((k1+1)*(k2+1),ans);
            }
        }*/
        for (int i=1;i<=sqrt(N);i++){
            if (N%i==0){
                ll a=i,b=(K+a-1)/a;
                if (b!=0){
                    ll k1=N/a,k2=M/b;

                    if (k1>0&&k2>0) ans = max(ans,k1-1+k2-1);
                }
                
                a=N/i,b=(K+a-1)/a;
                if (b!=0){
                    ll k1=N/a,k2=M/b;
                    if (k1>0&&k2>0) ans=max(ans,k1+k2-2);
                }
            }
        }
        for (int i=1;i<=sqrt(M);i++){
            if (M%i==0){
                ll a=i,b=(K+a-1)/a;
                if (b!=0){
                    ll k1=M/a,k2=N/b;
                    //cout<<a<<" "<<b<<" "<<k1<<" "<<k2<<endl;
                    if (k1>0&&k2>0)ans = max(ans,k1+k2-2);
                }
                a=M/i,b=(K+a-1)/a;
                if (b!=0){
                    ll k1=M/a,k2=N/b;
                    if (k1>0&&k2>0) ans = max(ans,k1+k2-2);
                }
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
} 
复制代码

 赛后补题

1007

惯性思维了。

开始看到这种完全图,满脑子Boruvka算法+优化选边,然后发现优化组间选边因为边是乘法根本做不出来(

实际上,这道题要发现的性质是,因为|ij||pipj|,在ij分别取ii+1的时候,每条边一定是比n小的(因为p是个排列)

其实,我们就可以发现,最小生成树的每条边都比n

ab<n,那么一定有一个数比n来的小

于是我们枚举那个数,可以在nn的时间内算出每一条边,然后跑一遍krusual就好了

值得注意的是,我们需要开个桶来排序,如果直接用sort的话,因为边数最坏是nn级别的,所以多一个logT(本机大概跑12s)

复制代码
#include<bits/stdc++.h>
using namespace std;
int fa[50005];
int p[50005],pos[50005];
struct Node{
    int u,v;
};
vector<Node> Edge[50005];
int Getfa(int x){return ((x==fa[x])?x:fa[x]=Getfa(fa[x]));}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        int N; 
        scanf("%d",&N);
        for (int i=1;i<=N;i++)
            Edge[i].clear();
        for (int i=1;i<=N;i++){
            scanf("%d",&p[i]);
            pos[p[i]]=i;
        }
        int mx = sqrt(N);
        for (int i=1;i<=N;i++)
            for (int j=i+1; j<=i+mx && j<=N; j++){
                int tmp;
                tmp = abs(i-j) * abs(p[i] - p[j]);
                if (tmp < N) Edge[tmp].push_back({i,j});
                tmp = abs(pos[i] - pos[j]) * abs(i-j);
                if (tmp < N) Edge[tmp].push_back({pos[i],pos[j]});
        }
        //sort(Edge.begin(),Edge.end(),temp);
        int cnt=0;
        long long ans = 0;
        for (int i=1;i<=N;i++)
            fa[i] = i;
        for (int i=1;i<=N;i++)
        for (auto nw : Edge[i]){
            int fx=Getfa(nw.u),fy=Getfa(nw.v);
            if (fx == fy) continue;
            fa[fx]=fy;
            cnt++;
            ans = ans+i;
            if (cnt == N-1) break;
        }
        printf("%d\n",ans); 
    }
    return 0;
}
复制代码

 1002

作为团队的数据结构手我太弱了(

 

dp[i]表示到i点的方案,转移显然是把所有的前面的合法点扔进来场上题读完就知道,拉个单调栈,然后分奇偶维护和,显然单调栈弹出的时候会影响一个区间,让合法变成不合法(因为最大/最小值的位置改变了)

现在问题来了,怎么维护这个过程?

我场上想的一个个暴力pop然后暴力跑结果T飞辣(

然而显然这个过程可以拉一个线段树来维护。

可以转移的位置显然最大最小值要同时满足条件

那么我们考虑一个pop的过程,最大值位置的改变,是不是意味着本来最大值位置在stk[top1]stk[top]的合法会因为这个pop而改变。

我们先把这部分改变扔掉,也就是stk[top1]+1 stk[top]这段区间中,位置和stk[top]共奇偶的位置,满足的条件数量被1

最小值同理就好了。

单调栈pop,pop完再同理的把这个元素insert到栈的末尾

但是还有一个需要注意的点

就是l r区间是ridiculous的时候,可以转移的点是dp[l1]

所以dp值的insert要偏移一个位置。

复制代码
#include <bits/stdc++.h>
const int fish = 998244353;
const int mxx = 3e5+5;
using namespace std;
struct Seg{
    int Tag[4*mxx],mx[4*mxx];
    long long Sum[4*mxx];
    void PushDown(int Now){
        int x=Tag[Now];
        mx[Now<<1] += x;
        mx[Now<<1|1] += x;
        Tag[Now<<1]+=x;
        Tag[Now<<1|1]+=x;
        Tag[Now] = 0; 
    }
    void Build(int Now,int l,int r){
        Tag[Now] = mx[Now] = Sum[Now] = 0;
        if (l==r) return;
        int mid = (l+r)>>1;
        Build(Now<<1,l,mid),Build(Now<<1|1,mid+1,r);
    }
    void Update(int Now,int l,int r,int L,int R,int val){
        //cout<<Now<<" "<<L<<" "<<R<<" "<<endl;
        if(L<=l&&r<=R){
            Tag[Now] += val;
            mx[Now]  += val;
            return;
        }
        int mid = (l+r)>>1;
        if (Tag[Now]) PushDown(Now);
        if (L<=mid) Update(Now<<1,l,mid,L,R,val);
        if (mid<R)  Update(Now<<1|1,mid+1,r,L,R,val);
        mx[Now] = max(mx[Now<<1],mx[Now<<1|1]);
        Sum[Now] = 0;
        if (mx[Now<<1] == mx[Now]) (Sum[Now] += Sum[Now<<1]+fish)%=fish;
        if (mx[Now<<1|1] == mx[Now]) (Sum[Now] += Sum[Now<<1|1]+fish)%=fish;
    }
    void Insert(int Now,int l,int r,int Pos,int val){
        if (l==r){
            Sum[Now] = val;
            return;
        }
        int mid =(l+r)>>1;
        if (Tag[Now]) PushDown(Now);
        if (Pos <= mid) Insert(Now<<1,l,mid,Pos,val);
        else Insert(Now<<1|1,mid+1,r,Pos,val);
        mx[Now] = max(mx[Now<<1],mx[Now<<1|1]);
        Sum[Now] = 0;
        if (mx[Now<<1] == mx[Now]) (Sum[Now] += Sum[Now<<1]+fish)%=fish;
        if (mx[Now<<1|1] == mx[Now]) (Sum[Now] += Sum[Now<<1|1]+fish)%=fish;
    }
}Tree[2];
int tp1,tp2;
int a[mxx],dp[mxx],stk1[mxx],stk2[mxx];
void Solve(){
    int N;
    scanf("%d",&N);
    for (int i=1;i<=N;i++)
        scanf("%d",&a[i]);
    Tree[0].Build(1,1,N);
    Tree[1].Build(1,1,N);
    tp1=tp2=0;
    dp[0]=1;
    for (int i=1;i<=N;i++){
        Tree[i&1].Insert(1,1,N,i,dp[i-1]);
        while (tp1 && a[i]>a[stk1[tp1]]){
            Tree[(stk1[tp1]&1)].Update(1,1,N,stk1[tp1-1]+1,stk1[tp1],-1);
            tp1--;
        }
        stk1[++tp1] = i;
        Tree[i&1].Update(1,1,N,stk1[tp1-1]+1,i,1);
        while (tp2 && a[i]<a[stk2[tp2]]){
            Tree[(stk2[tp2]&1)^1].Update(1,1,N,stk2[tp2-1]+1,stk2[tp2],-1);
            tp2--;
        }
        stk2[++tp2] = i;
        Tree[(i&1)^1].Update(1,1,N,stk2[tp2-1]+1,i,1);
        dp[i] = 0;
        //cout<<Tree[0].mx[1]<<" "<<Tree[1].mx[1]<<endl;
        if (Tree[0].mx[1] == 2) (dp[i] += Tree[0].Sum[1])%=fish;
        if (Tree[1].mx[1] == 2) (dp[i] += Tree[1].Sum[1])%=fish;
    }
    printf("%d\n",dp[N]);
    return;
}
int main(){
    //freopen("1002.in","r",stdin); 
    int T;
    scanf("%d",&T);
    while (T--){
        Solve();
    }
    return 0;
} 
复制代码

 8.15牛客

A

签到,双指针随便扫扫。

复制代码
#include<bits/stdc++.h>
using namespace std;
int N,M,cnt=0;
int a[100010];
int b[100010];
long long ans = 0;
int main(){
    scanf("%d%d",&N,&M);
    for (int i=1;i<=N;i++)
        scanf("%d",&a[i]);
    int r=1;
    for (int l=1;l<=N;l++){
        while (r<=N && cnt<M){
            if (b[a[r]] == 0) cnt++;
            b[a[r]]++;
            r++;
        }
        //cout<<l<<" "<<r<<" "<<cnt<<endl;
        if (cnt == M) ans = ans + (N-r+2);
        if (b[a[l]] == 1) cnt--;
        b[a[l]] --;
    }
    cout<<ans;
    return 0;
}
复制代码

B

首先两个人完全等价,只考虑一个人的情况

dp[i][j]表示到i,走了j

方程是dp[i][j]=dp[k][j1]1a[k](k能到i)

直接转移会T

考虑优化

优化的话,把每个跳跃看成一个线段,差分+前缀和即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
int a[9005],inv[9005];
vector<int> aa[8005];
const int fish = 998244353;
int Pow(int x,int y){
    int ans = 1;
    for(;y;y>>=1){
        if (y&1) ans = 1ll*ans*x%fish;
        x=1ll*x*x%fish;
    }
    return ans;
}
int dp[8005][8005];
int ans = 0;
int main(){
    int N;
    scanf("%d",&N);
    for (int i=1;i<N;i++){
        scanf("%d",&a[i]);
        inv[i] = Pow(a[i],fish-2);
    }
    //dp[1][1]=1;
    dp[1][0]=1;
    for (int i=1;i<=N;i++){
        for (int j=1;j<=N;j++)
            aa[j].clear();
        int x= dp[i][i-1]*1ll*inv[i]%fish;
        aa[i+a[i]].push_back(x);
        for (int j=i+1;j<N;j++){
            dp[j][i] = x;
            int tmp = 1ll*dp[j][i-1]*1ll*inv[j]%fish; 
            //cout<<j<<" "<<i-1<<" "<<dp[j][i-1]<<endl; 
            aa[j+a[j]].push_back(tmp);x=(x+tmp)%fish;
            for (auto nw : aa[j]){
                x = (x-nw+fish)%fish;
            }
        }
        ans = (ans + 1ll*x*x%fish)%fish; 
    }
    cout<<ans;
    return 0; 
}
复制代码

G

PAM模板

话说PAM这么普及了吗

PAM建出来

暴力跳fail遍历一下就好。

复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=300010,M=1e6+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first 
#define se second
string a = "",ss;
int len[N],fail[N],cnt=1,cur,num[N],ans,tri[N][55],u,Times[N];
int get_fail(int x,int i){
    while(i-len[x]-1<0||a[i-len[x]-1]!=a[i]) 
        x=fail[x];
    return x;
}
int main(){
    int N;
    memset(Times,0,sizeof(Times));
    cin>>N;
    
    for (int i=1;i<=N;i++){
        cin>>ss;
         a = a  + ss+ char('z' + 1 + i);
        }
        len[1]=-1,fail[0]=1;
        int n=a.length();
        int nw = 0;
        for(int i=0;i<n;i++){
            if (a[i]>'z'){
                nw++;
            }
             u=get_fail(cur,i);
            if(!tri[u][a[i]-'a']){
                len[++cnt]=len[u]+2; 
                fail[cnt]=tri[get_fail(fail[u],i)][a[i]-'a'];  
                tri[u][a[i]-'a']=cnt;
            }
            cur=tri[u][a[i]-'a'];
            Times[cur] |=(1<<nw);
        }
    int ST=(1<<N)-1;
    long long ans = 0;
    for (int i=cnt;i;i--){
        for (int x=i;x;x=fail[x]){
            Times[fail[x]] |= Times[x];
            if ((Times[fail[x]] & Times[x])== Times[x]) break;
        }
        if (Times[i] == ST) ans++;
    }
    cout<<ans;
    return 0;
}
复制代码

I

容易想到一个dp

dp[i][j]表示i划分到j

直接转移T

发现区间最大值相同的段可以合并转移

写一发单调栈就可以了。

注意每次弹栈的时候合并一下最大值区间就好了。

有点像前两天的一个题

复制代码
#include <bits/stdc++.h>
using namespace std;
int a[10005];
int dp[8005][8005];
int stk[8005],tp,ans[8006],dpmn[8005];
int N;
int main(){
    memset(dp,63,sizeof(dp));
    cin>>N;
    dp[0][0]=0;
    for (int i=1;i<=N;i++)
        scanf("%d",&a[i]);
    for (int i=1;i<=N;i++){
        tp=0;
        for (int j=i;j<=N;j++){
            int mn = dp[i-1][j-1];
            while (tp && stk[tp]<=a[j]){
                mn = min(mn,dpmn[tp]);
                tp--;
            }
            stk[++tp] = a[j];
            dpmn[tp] = mn;
            ans[tp] = mn+a[j];
            if (tp>1) ans[tp] = min(ans[tp],ans[tp-1]);
            dp[i][j] = ans[tp];
        }
    }
    for (int i=1;i<=N;i++){
        cout<<dp[i][N]<<endl;
    }
    return 0;
}
复制代码

 8.16 牛客

有点事,没怎么打。

1008

冷静思考一下会发现答案最大只有2,因为最差可以x>11>y

然后问题就变成统计同时和xy互质的数的个数

容斥一下即可。

复制代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
set<int> nw;
bool isPrime[10000010];
int Prime[10000010], mp[10000010],cnt = 0;
inline long long read()//inline 加速读入
{
    long long x=0;char c=getchar();//x代表返回值,c代表读取的字符
    while (c<'0'||c>'9') c=getchar();//读取所有非数部分
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();//如果读取的字符为数,加入返回值
    return x;
}

void Pre(int mx){
    for (int i = 2; i <= mx; i++){
        if (!isPrime[i]){
            Prime[++cnt] = i;
            mp[i]=i;
        }
        for (int j = 1; j <= cnt && i * Prime[j] <= mx; j++){
            isPrime[i * Prime[j]] = 1;
            mp[i*Prime[j]] = Prime[j];
            if (i % Prime[j] == 0)
                break;
        }
    }
}
void GetP(int x){
    while (x!=1){
        int y = mp[x];
        while (x%y==0) x/=y;
        if (nw.find(y) == nw.end()) nw.insert(y);
    }
}
vector<int> nww;
/*long long calc(long long x){
    long long ans=0;
    int sz=nww.size();
    for (int ST=1;ST<1<<sz;ST++){
        int cnt=0,m=1;
        for (int i=0;i<sz;i++)
            if (ST>>i&1)
                ++cnt,m*=nww[i];
        if (cnt & 1)
            ans+=x/m;
        else
            ans-=x/m;
    }
    return x-ans;
}*/
int N,Q;
int m,ret;
void dfs(int x,int s,int o){
    if (x==m){
        ret+=o*(N/s);
        return;
    }
    dfs(x+1,s,o);
    if (s<=N/nww[x]) dfs(x+1,s*nww[x],-o);
}
int main(){
    //freopen("input.txt","r",stdin);
    //freopen("test.out","w",stdout);
    N=read(),Q=read();
    Pre(N);
    while (Q--){
        int u,v; 
        u=read();v=read();
        if (__gcd(u,v) == 1){
            printf("1 1\n");
            continue;
        }
        nw.clear();nww.clear();
        GetP(u);GetP(v);
        for (auto x:nw)
            nww.push_back(x);
        m=nww.size();
        ret = 0;
        dfs(0,1,1);
        
        printf("%d %lld\n",2,ret+(__gcd(u,v) == 2));
    }
}
复制代码

dfs过了然而状压for T麻了,不是很理解为什么

1010

发现答案只有一种情况,是x+1的累乘1

复制代码
#include <bits/stdc++.h>
using namespace std;
const int fish = 998244353;
void Solve(){
    int N;
    long long ans = 1;
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        long long x;
        scanf("%lld",&x);
        ans = ans*(x+1)%fish;
    }
    printf("%lld\n",(ans-1+fish)%fish);
}
int main(){
    int T;
    cin>>T;
    while (T--)
        Solve();
} 
复制代码

 1007

dp[i][j]=dp[i1][j1]+dp[i1][j](jx) x是不能与这个套娃放在一组的套娃数量

这个拿个指针维护就行。

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
const int fish = 998244353;
long long dp[5005][5005];
int a[5005];
void Solve(){
    memset(dp,0,sizeof(dp));
    int N,K,r;
    scanf("%d%d%d",&N,&K,&r);
    for (int i=1;i<=N;i++)
        scanf("%d",&a[i]);
    dp[0][0]=1;
    long long pos = 0;
    for (int i=1;i<=N;i++){
        while (a[pos+1] <= a[i]-r && pos+1<i) pos++;
        for (int j=i-pos;j<=i;j++){
            dp[i][j] = (dp[i-1][j-1]+dp[i-1][j]*1ll*(j-(i-pos-1))%fish)%fish;
        }
    }
    cout<<dp[N][K]<<endl;
}
int main(){
    scanf("%d",&T);
    while (T--){
        Solve();
    }
}
复制代码

 8.17 牛客

E

出题人能不能把题意写清楚点……

先考虑最后P个空位,因为这P个人任意一个人动了的话,那就会导致自己成为倒数第P

所以最后P个人一定不会复读

以此类推,每次向前推P个位置,就会发现,只有前N个人会选择复读,后面的人都相互牵制。

然后就是这个题目题意写的不清楚的地方了(

其实每个人只能看到前面的人的选择,而不能看到后面

所以它只会能取则取。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,P;
int flag[5005];
int ans[5005];
int a[5005][5005];
int main(){
    scanf("%d%d",&N,&P);
    for (int i=1;i<=N;i++)
        for (int j=1;j<=N;j++)
            scanf("%d",&a[i][j]);
    for (int i=1;i<=N;i++){
        int mx=0,pos=0;
        if (i<=N%P) cout<<a[i][i]<<" ";
        else cout<<0<<" ";
    }
    return 0;
}
复制代码

H

发现后缀0的个数只和质因子中25的个数有关,是min(num2,num5)

然后问题就好做了。

考虑一个点的答案构成,显然是由子树的所有点和外面的点

而外面的点其实一定在x1的链上,可以在dfs的过程中顺便统计一下。

子树内的点直接算

然后就结束了

复制代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int N = 1e5 + 5;
const int INF = 0x3f3f3f3f;

int n, q;
ll cnt2[N], cnt5[N], ans[N], siz[N];
int e[N * 2], nex[N * 2], hed[N], ct;

void dfs1(int x, int fa) {
    siz[x] = 1;
    int k = x;
    while (!(k % 2)) {
        cnt2[x]++;
        k /= 2;
    }
    while (!(k % 5)) {
        cnt5[x]++;
        k /= 5;
    }
    for (int i = hed[x]; i; i = nex[i]) {
        if (e[i] == fa)
            continue;
        dfs1(e[i], x);
        siz[x] += siz[e[i]];
    }
}

void dfs2(int x, int fa, ll sum2, ll sum5) {
    ll Sum2 = sum2 + siz[x] * cnt2[x];
    ll Sum5 = sum5 + siz[x] * cnt5[x];
    ans[x] = min(Sum2, Sum5);
    for (int i = hed[x]; i; i = nex[i]) {
        if (e[i] == fa)
            continue;
        dfs2(e[i], x, sum2 + (siz[x] - siz[e[i]]) * cnt2[x], sum5 + (siz[x] - siz[e[i]]) * cnt5[x]);
    }
}

void solve() {
    cin >> n >> q;
    for (int i = 1; i < n; ++i) {
        int a, b; cin >> a >> b;
        e[++ct] = b, nex[ct] = hed[a], hed[a] = ct;
        e[++ct] = a, nex[ct] = hed[b], hed[b] = ct;
    }
    dfs1(1, 1);
    dfs2(1, 1, 0, 0);
    while (q--) {
        int a; cin >> a;
        cout << ans[a] << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    solve();
    return 0;
}
复制代码

J

考虑先破环成链

然后发现,因为这个赋值操作只能往前赋值。

所以其实每次只需要考虑两种操作下,这个点向前延伸最远能到哪就好。

fi表示从i点开始,向前延伸到的最远位置

记得多考虑一下可能出现增完之后,导致和前面的段相同进行的合并。

复制代码
#include<cstdio>
int T,n,a[2000050],f[2000050];
int main(){
    scanf("%d",&T);
    while (T--){
        int flag=0;
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for (int i=1;i<=n;i++)
            a[i+n]=a[i];
        f[1]=1;
        int m=n*2;
        for (int i=2;i<=m;i++){
            if (a[i]==a[i-1]||a[i]==(a[i-1]+1)%3)
                f[i]=f[i-1];
            else 
                f[i]=i;
            if (f[i]!=1&&a[i]==a[f[i]-1])f[i]=f[f[i-1]-1];
            if (i-f[i]+1>=n){
                flag=1;
                break;
            }
        }
        if (flag)printf("Yes\n");
        else printf("No\n");
    }
}
复制代码

M

签到,模拟

出题人能不能把题意写清楚点x2

复制代码
#include<cstdio>
double p[5][6]={{0,0,0,0,0,0},{0,1,1,0.8,0.5,0},{0,2,2,1.6,1,0},{0,3,3,2.4,1.5,0},{0,5,5,2.5,2,0}};
int c[15][15];
double ans;
int main(){
    for (int i=1;i<=4;i++)
        for (int j=1;j<=5;j++)
            scanf("%d",&c[i][j]);
    double sum1=0,sum2=0;
    for (int i=1;i<=4;i++){
        for (int j=1;j<=5;j++){
            sum1+=p[i][1]*c[i][j];
            sum2+=p[i][j]*c[i][j];
        }
    }
    ans+=sum2/sum1*100;
    sum1=sum2=0;
    for (int i=1;i<=5;i++)
        sum1+=c[4][i];
    sum2=c[4][1]*1.0+c[4][2]*0.5+c[4][3]*0.4+c[4][4]*0.3;
    ans+=sum2/sum1;
    printf("%.12lf\n",ans);
}
复制代码

 8.18杭电

 

我就是个寄吧

今天单刷

1001

竞赛排名问题,考虑网络流

首先,因为这个人想让1获胜

所以先考虑让1能赢则赢

然后考虑分配剩下的局数

发现,剩下的人在剩下的局里一定最多只能赢win1winx

考虑分配剩下的局数

把比赛当成点

然后S向比赛点连一条容量为1的边

比赛点向比赛双方连一条容量为1的边

然后每个人向Twin1winx容量的边

这样跑最大流之后,跑出来的东西就是能胜场数量

而胜场数量一定是和剩下的场次数量相等的(因为比赛一定会有输赢)

直接跑就好了。

复制代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+50;
const long long INF=2e18;
int n,m,s,t,ecnt;
int dis[5005],head[5005];
long long ans=0;
struct mint{
    int nxt,v;
    long long w;
} e[maxn<<1];
inline void addline(int u,int v,long long w) {
    e[ecnt].nxt=head[u];
    e[ecnt].v=v;
    e[ecnt].w=w;
    head[u]=ecnt++;
    e[ecnt].nxt=head[v];
    e[ecnt].v=u;
    e[ecnt].w=0;
    head[v]=ecnt++;
}
int BFS() {
    queue<int> q;
    memset(dis,0,sizeof(dis));
    dis[s]=1;
    q.push(s);
    while(!q.empty()) {
        int now=q.front();
        q.pop();
        for(int i=head[now]; ~i; i=e[i].nxt) {
            int v=e[i].v;
            if(e[i].w==0 || dis[v]) continue;
            dis[v]=dis[now]+1;
            q.push(v);
        }
    }
    return dis[t];
}

long long DFS(int now,long long lim) {
    if(now==t || !lim) return lim;
    long long res=0;
    for(int i=head[now]; ~i; i=e[i].nxt) {
        int v=e[i].v;
        if(e[i].w==0 || dis[v]!=dis[now]+1) continue;
        long long add=DFS(v,min(lim,e[i].w));
        e[i].w-=add;
        e[i^1].w+=add;
        res+=add;
        lim-=add;
        if(!lim) break;
    }
    if(!res) dis[now]=-1;
    return res;
}
int Dinic()  
{  
    int ans=0;  
    while (BFS()) {
        ans+=DFS(s,1e6);
    }
    return ans;  
}
int win[5005];
int u[5005],v[5005];
int p[5005];
int main()  
{
    int TT;
    scanf("%d",&TT);
    while (TT--){
        int N,M1,M2;
        scanf("%d%d%d",&N,&M1,&M2);
        ecnt = 0;
        int Point = N;
        s=1;
        for (int i=1;i<=5000;i++)
            head[i] = -1;
        memset(win,0,sizeof(win));
        for (int i=1;i<=M1;i++){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            if (z == 1) win[x]++;
            else win[y]++;
        }
        for (int i=1;i<=M2;i++){
            scanf("%d%d",&u[i],&v[i]);
            if (u[i] == 1 || v[i] == 1)
                win[1]++;
        }
        int cntt = 0;
        for (int i=1;i<=M2;i++){
            if (u[i] == 1 || v[i] == 1) continue;
            Point ++;
            addline(s,Point,1);
            addline(Point,u[i],1);
            addline(Point,v[i],1);
            cntt++;
        }
        t=++Point;
        bool flag = false;
        for (int i=2;i<=N;i++){
            if (win[1] < win[i]){
                flag = true;
                break;
            }
            addline(i,t,win[1]-win[i]);
        }
        if (flag){
            printf("NO\n");
            continue;
        }    
        //    cout<<"qwq"<<endl;
        if (Dinic() == cntt){
            printf("YES\n");
        }
        else printf("NO\n");
    }
    return 0;     
}  
复制代码

1003

题目很唬人但其实只有两种可能

一种是大小交替,一种是小大交替

模拟即可。

记得等号是要取的

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N;
long long a[1000006],b[1000006];
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            scanf("%lld",&a[i]);
            b[i] = a[i];
        }
        long long ans1=0,ans2=0;
        for (int i=2;i<=N;i++){
            if (i&1){
                if (a[i] <= a[i-1]){
                    ans1 += abs(a[i]-(a[i-1]+1));
                    a[i] = a[i-1]+1;
                }
                if (b[i]>=b[i-1]){
                    ans2 += abs(b[i]-(b[i-1]-1));
                    b[i] = b[i-1]-1;
                }
            }
            else{
                if (a[i]>=a[i-1]){
                    ans1 += abs(a[i]-(a[i-1]-1));
                    a[i] = a[i-1]-1;
                }
                if (b[i]<=b[i-1]){
                    ans2 += abs(b[i]-(b[i-1]+1));
                    b[i] = b[i-1]+1;
                }
            }
        }
        long long ans = min(ans1,ans2);
        printf("%lld\n",ans);
    }
    return 0;
}
复制代码

1007

奇数块分不出合法解

问题转化成所有两边都是偶数的边的个数为cnt

答案为2cnt1

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
vector<int> f[N];
int du[N];
int ans[N];

int n;
int u, v;
int aim;
int number = 0;
const int fish = 998244353;
int Pow(int x,int y){
    int anss = 1;
    for (;y;y>>=1){
        if (y&1) anss = 1ll*anss*x%fish;
        x=1ll*x*x%fish;
    }
    return anss;
}
int getans(int x, int pre)
{
        ans[x] = 1;
        int len = f[x].size();
        if (len == 1 && x != aim)
        {
                return ans[x];
        }
        for (int i = 0; i < len; i++)
        {
                int to = f[x][i];
                if (to == pre)
                {
                        continue;
                }
                getans(to, x);
                ans[x] += ans[to];
        }
        if(ans[x]%2==0&&ans[x]!=n)
        number++;
        return ans[x];
}

void Solve(){
        number = 0;
        scanf("%d",&n);
        if (n%2)
        {
                cout << -1 << endl;
                return;
        }
        for (int i=1;i<=n;i++){ 
            f[i].clear(),f[i].clear();
            du[i]=0;
            ans[i] = 0;
        }
        for (int i = 1; i <= n - 1; i++)
        {
                scanf("%d",&u),scanf("%d",&v);
                f[u].push_back(v);
                f[v].push_back(u);
                du[u]++, du[v]++;
        }
        for (int i = 1; i <= n; i++)
        {
                if (du[i] == 1)
                {
                        aim = i;
                        getans(i, -1);
                        break;
                }
        }
        printf("%d\n",Pow(2,number)-1);
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--){
        Solve();
    }
    return 0;
}
复制代码

1009

读完题

猜测一下最优策略

会发现,一定是黑 白 黑 和 黑 白 白 黑两种情况交错

然后回发现,AliceBob的先手的情况下,涂色是以7为一个周期的

Alice会先涂左手第二个,而Bob会先涂左手第三个

根据这个去把循坏节推出来就行了。

虽然现场是交了几发wa调出来的循环节(

 

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
string S;
int Alice[]={0,1,1,1,2,2,3};
int Bob[] = {0,1,1,2,2,3,3};
int main(){
    int T;
    cin>>T;
    while (T--){
        cin>>N>>S;
        int ans = 3*(N/7);
        if (N%7 !=0){
            if (S=="Alice")ans = ans + Alice[N%7];
            else ans = ans + Bob[N%7];
        }
        printf("%d\n",ans);
    }
}
复制代码

 

posted @   si_nian  阅读(131)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
欢迎阅读『2022暑期多校(牛客+杭电)』
点击右上角即可分享
微信分享提示