【模拟赛】纪中提高A组 19.8.9 测试

Task.1 走格子

题目大意:C和F在一个可以看作 \(N\times M\) 的矩阵的房间中,矩阵中的每一个元素可以是:

  1. 障碍:"#"
  2. C或者F的起点:"C"或"F"
  3. 空区域:"."

C携带了一把传送枪,每次C都可以:

  1. 花费一个单位时间移动到相邻的空区域
  2. 不花费时间向上下左右之一的方向的墙壁上发射传送门(传送门最多只能同时存在两扇,如果已经存在两扇再发射一扇那最早出现的那扇会消失,一个位置不能存在两扇传送门)
  3. 花费一个单位时间进入相邻墙壁上的传送门中移动到另一个传送门前方的格子中。

求C到F的最短时间。

数据范围:\(1\leq N,M\leq 500\),保证地图最外围是障碍。

https://store.steampowered.com/app/400/Portal/

上来我们就想想爆搜?能过吗?可以拿到85pts的好成绩???

正确的操作是这样的:当你在某个位置时,你可以向四个方向上发射两个传送门,一定可以走到那个最近的墙边进传送门到另外一面墙前面。

做法就很简单了:bfs或最短路算法,考虑一下上述情况就可以了。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<utility>
using namespace std;

template<class T>void read(T &x){
    x=0; char c=getchar();
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef pair<int,int> pr;
const int N=505;

int n,m,s,t;
char mp[N][N];
vector<pr>e[N*N];
int num[N][N],L[N][N],R[N][N],U[N][N],D[N][N];
int dis[N*N];
priority_queue<pr>q;
bool vis[N*N];
int dx[5]={0,0,1,-1};
int dy[5]={1,-1,0,0};

void add(int x,int y,int w){e[x].push_back(pr(y,w));}
bool dijkstra(){
    memset(vis,0,sizeof(vis));
    memset(dis,0x7f,sizeof(dis));
    dis[s]=0; q.push(pr(0,s));
    pr now; int x,y,d;
    while(!q.empty()){
        now=q.top(); q.pop();
        x=now.second; if(vis[x])continue; vis[x]=1;
        for(int i=e[x].size()-1;~i;i--){
            y=e[x][i].first; d=e[x][i].second;
            if(dis[y]>dis[x]+d){
                dis[y]=dis[x]+d;
                if(!vis[y])q.push(pr(-dis[y],y));
            }
        }
    }
    return dis[t]!=dis[0];
}
int main(){
    freopen("cell.in","r",stdin);
    freopen("cell.out","w",stdout);
    read(n); read(m); int cnt=0;
    for(int i=1;i<=n;i++){
        scanf("%s",mp[i]+1);
        for(int j=1;j<=m;j++){
            if(mp[i][j]=='#'){L[i][j]=R[i][j]=U[i][j]=D[i][j]=-1; continue;}
            num[i][j]=++cnt;
            if(mp[i][j]=='C') s=num[i][j];
            if(mp[i][j]=='F') t=num[i][j];
            L[i][j]=L[i][j-1]+1; U[i][j]=U[i-1][j]+1;
        }
    }
    for(int i=n;i;i--) for(int j=m;j;j--) if(mp[i][j]!='#'){R[i][j]=R[i][j+1]+1; D[i][j]=D[i+1][j]+1;}
    int nx,ny,d;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) if(mp[i][j]!='#'){
            for(int k=0;k<4;k++){
                nx=i+dx[k]; ny=j+dy[k];
                if(mp[nx][ny]=='#')continue;
                add(num[i][j],num[nx][ny],1);
            }
            d=min(min(L[i][j],R[i][j]),min(U[i][j],D[i][j]))+1;
            add(num[i][j],num[i][j-L[i][j]],d);
            add(num[i][j],num[i][j+R[i][j]],d);
            add(num[i][j],num[i-U[i][j]][j],d);
            add(num[i][j],num[i+D[i][j]][j],d);
        }
    }
    if(dijkstra()) printf("%d\n",dis[t]);
    else puts("wtf");
    return 0;
}

Task.2 扭动的树

题目大意:有一棵 \(key\) 为键值 \(val\) 为权值 \(n\) 个点的二叉查找树,定义某个节点的 \(sum\) 值为它的子树内的 \(val\) 的和。告诉你 \(n\) 个节点的 \(key\) 值和 \(val\) 值,求满足树上任意一条边两个端点 \(key\) 值最大公约数不为 \(1\) 时,树上所有节点的 \(sum\) 值的和最大是多少。

数据范围:\(1\leq N\leq 300,1\leq key_i\leq 10^18,1\leq val_i\leq 10^6\)

一棵二叉搜索树的中序遍历就是所有键值排序后的结果。试着排序后的 \(n\) 个元素进行合并,可以使用类似区间DP的思路,定义状态 \(f_{i,j,k}\) 表示区间 \([i,j]\) 合并成一棵子树根为 \(k\) 的最大答案,转移类似区间DP,复杂度 \(O(n^4)\)。

对这个思路进行优化:区间 \([i,j]\) 以 \(k\) 为根的情况最终一定可以由 \([i,k-1]\) 和 \([k+1,j]\) 得到。所以我们试着把上述DP的第三维改进一下,定义 \(f_{i,j,0/1}\) 表示区间 \([i,j]\) 以 \(i-1\) 或 \(j+1\) 为根的答案,转移依然是类似区间DP的,枚举根 \(k\),复杂度 \(O(N^3)\)。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<utility>
using namespace std;
template<class T>void read(T &x){
    x=0; char c=getchar();
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef long long ll;
typedef pair<ll,ll> pr;
const int N=305;
#define key first
#define val second
int n;
pr a[N];
ll sum[N],f[N][N][2],ans;
bool e[N][N];
void cmax(ll &x,ll y){if(x<y)x=y;}
ll gcd(ll x,ll y){return y==0?x:gcd(y,x%y);}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    read(n);
    for(int i=1;i<=n;i++){read(a[i].key); read(a[i].val);}
    sort(a+1,a+n+1);
    if(a[1].key==1){puts("-1"); return 0;}
    memset(f,-0x3f,sizeof(f));
    for(int i=1;i<=n;i++){
        sum[i]=sum[i-1]+a[i].val;
        for(int j=i+1;j<=n;j++)
            e[i][j]=(gcd(a[i].key,a[j].key)!=1);
        if(e[i-1][i]) f[i][i][0]=a[i].val;
        if(e[i][i+1]) f[i][i][1]=a[i].val;
    }
    for(int len=2;len<=n;len++)
        for(int i=1,j=i+len-1;j<=n;i++,j++){
            ll cost=sum[j]-sum[i-1];
            for(int k=i;k<=j;k++){
                ll add=(i<k)*f[i][k-1][1]+(k<j)*f[k+1][j][0]+cost;
                if(e[i-1][k]) cmax(f[i][j][0],add);
                if(e[k][j+1]) cmax(f[i][j][1],add);
                if(len==n) cmax(ans,add);
            }
        }
    printf("%lld\n",ans);
    return 0;
}

Task.3 旋转字段

题目大意:定义一个排列的价值是排列中 \(p_i=i\) 这样的固定点的个数。现在和以选择一个区间 \([L,R]\) 翻转,求翻转后最多的固定点。

数据范围:\(1\leq N\leq 10^5\)

有这么一个性质:在位置 \(i\) 上的数字 \(x\) 关于 \(\frac{(i+x)}{2}\) 翻折会产生 \(1\) 的贡献,并且某个位置上的数它会产生贡献的对称点都是固定的。再考虑一下最后答案翻转的区间长什么样子:区间左右端点至少有一个关于对称点(区间中点)翻转后会产生贡献,否则如果选了这两个端点一定不会是答案变优。

根据上述的分析,我们想到了一个贪心做法:把相同对称点的点放在一起做,从小到达枚举区间半径,利用前缀和差分得到翻转区间之外的答案,复杂度 \(O(N^2)\) 。还不够好,我们发现枚举半径时有很多多余的情况,把这些情况去掉,每次由近至远枚举区间端点,跳过翻转后不产生贡献的点,计算答案方式和上面相同。这样每个位置只被考虑一次,复杂度 \(O(N)\)。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;

template<class T>void read(T &x){
    x=0; char c=getchar();
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef pair<int,int> pr;
void cmax(int &x,int y){if(x<y)x=y;}
void cmin(int &x,int y){if(x>y)x=y;}
const int N=100050;
int n,a[N],pre[N],ans;
vector<int>q[N<<1];
int main(){
    freopen("rotate.in","r",stdin);
    freopen("rotate.out","w",stdout);
    read(n);
    for(int i=1;i<=n;i++){
        read(a[i]); pre[i]=pre[i-1]+(a[i]==i);
        if(a[i]>i) q[a[i]+i].push_back(a[i]);
        else q[a[i]+i].push_back(i);
    }
    int L,R;
    for(int i=(n<<1);i>1;i--){
        sort(q[i].begin(),q[i].end());
        for(int j=0;j<q[i].size();j++){
            R=q[i][j]; L=i-R;
            cmax(ans,pre[L-1]+pre[n]-pre[R]+j+1);
        }
    }
    printf("%d\n",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/opethrax/p/11333979.html

时间: 2024-10-18 00:39:25

【模拟赛】纪中提高A组 19.8.9 测试的相关文章

【模拟赛】纪中提高A组 19.8.17 测试

Task.1 倾斜的线 题目大意:在平面上有 \(n\) 个点,给定 \(P,Q\),在平面上找到两个点使它们所在直线的斜率数值上最接近 \(\frac{P}{Q}\) .对于接近程度相同的直线选择较小的斜率. 数据范围:\(2\leq N\leq 2\times 10^5,1\leq P,Q,x,y\leq 10^9\),不存在经过两点的直线斜率为 \(0\) 或正无穷. 在考场想到了一个没那么naive的想法,把坐标系旋转到 \(y\) 轴平行于斜率为 \(\frac{P}{Q}\) 的直线

纪中某日c组模拟赛 2314. 最短路

2314. 最短路 (File IO): input:dti.in output:dti.out 时间限制: 1000 ms  空间限制: 262144 KB  具体限制 Goto ProblemSet 题目描述 输入 样例输出 数据范围限制 原文地址:https://www.cnblogs.com/send-off-a-friend/p/11359152.html

【题解】JZOJ 提高A组 19.8.10 挖宝藏

二进制枚举子集 先给出代码: for(int o = s; o; o = (o - 1) & s) 其中\(s\)为当前的状态,\(o\)为枚举的子集.根据与运算的性质我们得到的显然是s的子集,但是为什么这样做可以得到\(s\)所有的子集? 网上的一种说法是把状态\(s\)看做忽略\(0\)的二进制数,只考虑每次对这个二进制数减一,过程大概是: 假如 \(s=(0101101)_2\quad s_0=(1111)_2\) \(s_1=(s_0-1)\&s=(1110)_2\) \(s_2=

【NOIP 模拟赛】中值滤波 打表找规律

对于这样看起来不像什么算法也没什么知识点的题,一脸懵逼的话不是手推规律就是打表找规律......... 当然还有一些超出你能力之外的数学题...... #include <cstdio> const int N=500010; int n,ans,A[N]; inline int Max(int x,int y){ return x>y?x:y; } int main(){ scanf("%d",&n); int last,now,P=0; for(int i

【前行】◇第3站◇ 国庆训练营&#183;OI制模拟赛

[第3站] 国庆训练营·OI制模拟赛Ⅰ 怀着冲刺提高组400的愿望来到这个very small but very interesting 的训练营QwQ 在北大dalao的带领下开始了第一场OI模拟赛[炸心态ヽ(*.>Д<)o゜] ? 简单总结 感觉非常爆炸…… 第一题还好,一眼看出结论题,所以开始打表……没想到只打出来了一种情况(为什么全是特殊情况),然后就凉了. 第二题就开始崩溃了.首先画图思考了大概20分钟……然后发现想不出正解,就开始想要骗分.看了看数据阶梯,发现自己好像只能做前1/3

2017.11.25【NOIP提高组】模拟赛A组

2017.11.25[NOIP提高组]模拟赛A组 T1 3467. [NOIP2013模拟联考7]最长上升子序列(lis) T2 3468. [NOIP2013模拟联考7]OSU!(osu) T3 3472. [NOIP2013模拟联考8]匹配(match) T1 有转移方程f[i]=max{f[j]}+1,a[j]<a[i] 可以用线段树+离散化维护这个方程,因为涉及以往状态可以用主席树维护 打太丑爆空间了 Code 1 #include<cstdio> 2 #include<c

2017.12.02【NOIP提高组】模拟赛A组

2017.12.02[NOIP提高组]模拟赛A组 T1 3555[GDKOI2014模拟]树的直径 T2 3542[清华集训2014]冒泡排序 T3 3486[NOIP2013模拟联考10]道路改建(rebuild) T1 树直径的一个性质,两棵树合并,形成新的树的直径的两个端点为原树中的四个端点之二. 可以用反证法证明.用此性质本题就变成了lca裸题了 Code #include<cstdio> #include<cstring> #include<cmath> #i

2017.12.09【NOIP提高组】模拟赛A组

2017.12.09[NOIP提高组]模拟赛A组 T1 3489. [NOIP2013模拟联考11]数列的GCD(gcd) T2 3500.[NOIP2013模拟联考15]物语(monogatari) T3 3501.[NOIP2013模拟联考15]消息传递(news) 吐槽:这次的题好像有点水啊,但最简单的第二题都给打挂啦!!(数组开小了) T1 本套题中最难的题.考虑dp 设f[i]是b[1],b[2]...b[N]的最大公约数的数目,g[i]是b[1],b[2]...b[N]的公约数的数目

ZROI提高组模拟赛05总结

ZROI提高组模拟赛05总结 感觉是目前为止最简单的模拟赛了吧 但是依旧不尽人意... T1 有一半的人在30min前就A掉了 而我花了1h11min 就是一个简单的背包,我硬是转化了模型想了好久,生生把一个弱智题变成了一个不可做题 最后竟然在转化两次后的模型上以为自己做出来了 这个题比别人多花的1h左右的时间,而且只得到了30分,成为了这场比赛失败的关键因素 T2 依旧是一道简单题 有人20min之内就A掉了 感觉放在CF里最多算一道Div2 D,还是简单的那种 可是我又一次想复杂了 大意就是