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 (伦敦大雾)
完结撒花
我怎么这么菜