9.22 NOIP模拟赛总结

T1

上来一看题先画了一个丑陋的图理解了题意

然后发现是一层一层点亮的,而且对角线也会直接点亮

需要处理的只有竖着的长条

以为是排列组合简单题结果发现点亮不能交叉

怎么办呢

手模一下出来几个数感觉有规律

长度为四不想模了

遂逃去写暴搜 不豪孩子们暴搜代码找不到了

然后发现神秘规律: \(f_i=f_{i-2}*3-f_{i-1}\)
直接预处理做完了,后面 \(10^{18}\) 的部分直接快速幂

code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,p,ans[5000005],sum=1;
int pow1(int x,int y){
    int ans=1;
    while(y){
        if(y&1)ans=ans*x%p;
        x=x*x%p,y>>=1;
    }
    return ans;
}
signed main(){
    //freopen("fireworks.in","r",stdin);
    //freopen("fireworks.out","w",stdout);
    cin>>n>>m>>p;
    if(n>m)swap(n,m);
    ans[1]=1,ans[2]=3;
    for(int i=3;i<=5000000;i++){
        ans[i]=(ans[i-1]*3%p-ans[i-2]+p)%p;
    }
    for(int i=2;i<=n-1;i++){
        sum=sum*ans[i]%p*ans[i]%p;
        //cout<<i<<" "<<sum<<endl;
    }
    if(m>n)sum=sum*pow1(ans[n],m-n)%p;
    // for(int i=n+1;i<=m;i++){
        //sum=sum*ans[n]%p;
    // }
    cout<<sum;
}

T2

理解完题直接想到拿manacher转化成线段问题,以为直接做就能做完了,就先去后面看了会题

然后

#include<bits/stdc++.h>
using namespace std;
int n,m,len,p[3005];
char a[3005][3005],s[3005];
struct T{
	int x,l,r;
};
struct TT{
    int x,y;
};
vector<T>lie,hang;
void manacher(int x,int id){
	int c,r;
	c=r=0;
	for(int i=1;i<=len;i++){
        if(i<r){
            p[i]=min(p[c]+c-i,p[(c<<1)-i]);
        }else{
            p[i]=1;
        }
        while(s[i+p[i]]==s[i-p[i]]){
            p[i]++;
        }
        if(i+p[i]>r){
            r=i+p[i];
            c=i;
        }
		if(!(s[i]=='#')||!(p[i]==1)){
			if(id==1)lie.push_back(T{(x,i-p[i]+2)/2,(i+p[i]-2)/2});
			else hang.push_back(T{(x,i-p[i]+2)/2,(i+p[i]-2)/2});
		}
    }
}
bool cmp(T x,T y){
    return x.r-x.l+1>y.r-y.l+1;
}
struct cmpp{
    bool operator()(TT x,TT y){
        return (lie[x.x].r-lie[x.x].l+1)*(hang[x.y].r-hang[x.y].l+1)>(lie[x.x].r-lie[x.x].l+1)*(hang[x.y].r-hang[x.y].l+1);
    }
};
int main(){
	//freopen("mat.in","r",stdin);
	//freopen("mat.out","w",stdout);
	cin>>n>>m;
	// for(int i=1;i<=n;i++){
	// 	for(int j=1;j<=m;j++){
	// 		cin>>a[i][j];
	// 	}
	// }
	// for(int i=1;i<=n;i++){
	// 	for(int j=1;j<=m;j++){
	// 		s[j*2-1]='#',s[j*2]=a[i][j];
	// 	}
	// 	len=m*2+1;
	// 	s[m*2]='#';
	// 	manacher(i,1);
	// }
	// for(int i=1;i<=m;i++){
	// 	for(int j=1;j<=n;j++){
	// 		s[j*2-1]='#',s[j*2]=a[i][j];
	// 	}
	// 	len=n*2+1;
	// 	s[n*2]='#';
	// 	manacher(i,2);
	// }
	// //问题变成了平面上横着竖着有一些线段,问垂直相交线段长度乘积的最值
	// //只能想到扫描线+线段树 维护一个单点修改,区间最值
	// //那好吧90分也不错往下写吧
	// //time:9:40
	// //诶不对我扫描线怎么删除线段
	// //c我还是写二分hash吧维护点然后O(nmlogn)预处理,O(nmlogn)累计答案,用线段树维护最值
	// //我服了是假的
	// //想写T4了溜了
	// //time:10:20
    // //啊怎么都11:20了
    // sort(lie.begin(),lie.end(),cmp);
    // sort(hang.begin(),hang.end(),cmp);
    // priority_queue<TT,vector<TT>,cmpp>q;
    // q.push(TT{0,0});
    // while(!q.empty()){
    //     TT now=q.top();
    //     q.pop();
    //     T x=lie[now.x],y=hang[now.y];
    //     if(x.l<=y.x&&y.x<=x.r&&y.l<=x.x&&x.x<=y.r){
    //         cout<<(x.r-x.l+1)*(y.r-y.l+1);
    //         return 0;
    //     }
    //     q.push(TT{now.x+1,now.y});
    //     q.push(TT{now.x,now.y+1});
    // }
	cout<<n*m;
}

如你所见

时间不够只能瞎搞,结果RE,全白写了

最后维护先把包含关系判掉,然后暴力做做完了

复杂度莫名奇妙的正确而且还很优

code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,len,p[10005],lie[3005][3005],hang[3005][3005],f1[3005][3005],f2[3005][3005],ans;
char a[3005][3005],s[10005];
void manacher(int x,int id){
	int c,mxr;
	c=mxr=0;
	memset(p,0,sizeof(p));
	for(int i=1;i<=len;i++){
        if(i<mxr){
            p[i]=min(p[c]+c-i,p[(c<<1)-i]);
        }else{
            p[i]=1;
        }
        while(s[i+p[i]]==s[i-p[i]]){
            p[i]++;
        }
        if(i+p[i]>mxr){
            mxr=i+p[i];
            c=i;
        }
		int l=(i-p[i]+2)/2,r=(i+p[i]-2)/2;
		if(p[i]>1&&hang[x][r]==0&&id==1){
			hang[x][r]=l;
		}else if(p[i]>1&&lie[x][r]==0&&id==2){
			lie[x][r]=l;
		}
    }
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		s[0]='*';
		for(int j=1;j<=m;j++){
			s[j*2-1]='#',s[j*2]=a[i][j];
		}
		len=m*2+1;
		s[len]='#';
		manacher(i,1);
	}
	for(int i=1;i<=m;i++){
		s[0]='*';
		for(int j=1;j<=n;j++){
			s[j*2-1]='#',s[j*2]=a[j][i];
		}
		len=n*2+1;
		s[len]='#';
		manacher(i,2);
	}
	for(int i=1;i<=n;i++){
		int l=m+1;
		for(int j=m;j>=1;j--){
			if(l<=hang[i][j]||hang[i][j]==0)continue;
			l=hang[i][j];
			for(int k=l;k<=j;k++){
				f1[i][k]=max(f1[i][k],j-l+1);
			}
		}
	}
	for(int i=1;i<=m;i++){
		int l=n+1;
		for(int j=n;j>=1;j--){
			if(l<=lie[i][j]||lie[i][j]==0)continue;
			l=lie[i][j];
			for(int k=l;k<=j;k++){
				f2[k][i]=max(f2[k][i],j-l+1);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			ans=max(ans,f1[i][j]*f2[i][j]);
		}
	}
	cout<<ans;
}

啊啊啊挂了95分不然就 rank2 了(雾)

T3

一看题觉得是分讨+推式子,分了一会觉得很神经就跳了,听完讲解懂了一半,不想碰...

T4

这题能说的好多啊

先把 \(Cornercase\) 判掉,然后就有了一些神秘的性质

注意到每次人走完 100% 要回来,于是把边权乘2就可以视作人和龙分着走,但是龙遇上人会顺便把他带走

然后注意到龙可以一直飞到终点,如果游更好但是走特别劣,就尽可能的游然后穿插飞

考虑走能让答案更优的情况

首先走的时间+游的时间必须小于飞的时间否则退化成上面的情景

那就类似上面的情况直接游然后等到游不动时,向后查找有没有一个点使得人走到那后龙也能直接游到那

如果没有就穿插飞,然后再找

其实复杂度有点问题,但是正确性显然(?)

code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int c,T,n,d,t0,t1,t2,a[500005],num;
signed main(){
	//freopen("dragon.in","r",stdin);
	//freopen("dragon.out","w",stdout);
    cin>>c>>T;
    while(T--){
        memset(a,0,sizeof(a));
        cin>>n>>d>>t0>>t1>>t2;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            num+=a[i];
        }
        if(t1<t0){
            cout<<(n-1)*t1<<endl;
            continue;
        }
        int now=0,ans=0;
        for(int i=1;i<n;i++){
            now+=a[i];
            //cout<<i<<" "<<now<<endl;
            if(now>=d){
                now-=d;
                ans+=t0;
            }else{
                if(t2*2+t0>t1){
                    cout<<ans+(n-i)*t1<<endl;
                    //cout<<1<<endl;
                    ans=-1;
                    break;
                }
                int sum=now,j;
                for(j=1;i+j<=n;j++){
                    sum+=a[i+j]-d;
                    //cout<<j<<" "<<sum<<endl;
                    if(sum>0||i+j==n&&sum>=0)break;
                }
                if(i+j>n){
                    now=0;
                    for(i=i+1;i<=n;i++){
                        //cout<<i<<" "<<a[i]<<" "<<ans<<endl;
                        if(a[i]>=d){
                            i--;
                            break;
                        }
                        ans+=t1;
                    }
                }
                //cout<<j<<" "<<i+j<<endl;
                ans+=j*(t2*2+t0);
                now=sum-a[i+j+1];
                i+=j;
            }
        }
        if(ans!=-1){
            cout<<ans<<endl;
        }
    }
}

问题就出在这

人走到哪龙不一定非的游到哪,它也可以游到 \(i-1\) 再飞到 \(i\)

然后就全乱了

所以贪心倒闭了

\(dp\) 才有前途

定义 \(f_{i,0/1}\) 表示飞到了 \(i\) ,当前岛上是否有体力的最小时间

枚举 \(j\) 并从 \(j\) 转移,0/1先不用考虑,这一维只会影响贡献的计算

那么转移就是 \(f_i \gets f_j + w(i,j)\)

其实就是枚举飞的两点,然后让中间算一个最优时间

具体的是前面说的方式

转移似乎推一推可以 \(O(1)\)

复杂度 \(O(n^2)\)

其实只有40分,最后一步是拿线段树优化这个 \(dp\) 做到 \(O(nlogn)\),但是我没研究这个

啊啊啊细节全想好了差一个情况不然我的贪心就是对的了(哭)
好吧暴力向后找复杂度有点问题但是没关系很好处理


最后 \(100 + 5 + 0 + 20 = 125\)\(rank 6\) 也还行吧 但我还是想要我的rank2 (伦敦大雾)

完结撒花

我怎么这么菜

posted @ 2025-09-23 09:52  __Vinson  阅读(10)  评论(0)    收藏  举报