NOIP提高组模拟赛18

求和

等差数列求和,直接乘会炸\(ull\),需要龟速乘(考试时候懒得打高精主要是不会,也忘了有龟速乘这个东西.......)

code
#include<cstdio>
#include<cstring>

using namespace std;

typedef unsigned long long ull;
ull x,y,x2,y2,mod;

ull slow(ull x,ull y){
    ull ans=0;
    while(y){
        if(y&1)ans=(ans+x)%mod;
        x=(x+x)%mod;
        y>>=1;
    }
    return ans;
}

int main()
{
    freopen("sum.in","r",stdin);
    freopen("sum.out","w",stdout);
    
    scanf("%llu%llu%llu%llu%llu",&x,&y,&x2,&y2,&mod);
    ull ans=(x+y-1);
    ull h=x2-x+1;
    ull z=y2-y+1;
    ull ghs=slow(h,z);
    ans=slow(ans,ghs);
    ull h1=h-1,h2=h;
    if(h&1)h1>>=1;
    else h2>>=1;
    ull yh=slow(h1,h2);
    ans=(ans+slow(yh,z))%mod;
    ull z1=z-1,z2=z;
    if(z&1)z1>>=1;
    else z2>>=1;
    ull res=slow(z2,z1);
    res=slow(res,h);
    ans=(ans+res)%mod;
    printf("%llu\n",ans);
    return 0;
}

B. 分组配对

有个贪心就是能多选一定多选,然后因为区间连续,就从左边开始判断即可。

因为排序不等式,最大的与最大的相乘,次大的与次大的相乘......这样得到的是最大值

显然满足二分性,但是直接二分会$TLE4

优化是倍增+二分,看起来复杂了,但是时间复杂度确实小了

先倍增锁定二分区间,然后二分,\(check\)不变

code
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

typedef  long long ll;
const int maxn=500005;
ll n,m;
ll a[maxn],b[maxn];
ll aa[maxn],bb[maxn];

bool check(int l,int r){
    for(int i=l;i<=r;++i)aa[i]=a[i];
    for(int i=l;i<=r;++i)bb[i]=b[i];
    sort(aa+l,aa+r+1);
    sort(bb+l,bb+r+1);
    bool flag=1;
    ll sum=0;
    for(int i=l;i<=r;++i){
        sum+=1ll*aa[i]*bb[i];
        if(sum>m){
            flag=0;
            break;
        }
    }
    return flag;
}


int work(){
    int now=1,ans=0;
    while(now<=n){
        int l=now,r=n;
        for(int i=1;now+(1<<i)-1<=n;++i)
          if(!check(now,now+(1<<i)-1)){
              l=now+(1<<(i-1))-1;
              r=now+(1<<i)-1;
              break;
          }
        
        while(l<r){
            int mid=(l+r+1)>>1;
            if(check(now,mid))l=mid;
            else r=mid-1;
        }
        ++ans;
        now=l+1;
    }
    return ans;
}

int main()
{
    freopen("pair.in","r",stdin);
    freopen("pair.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    for(int i=1;i<=n;++i)scanf("%lld",&b[i]);
    printf("%d\n",work());
    return 0;
}

C. 城市游戏

神奇的\(dp\),有的地方写的挺假的,有些情况根本没考虑,但是仍然能\(A\)\(Cd\)大佬想\(hack\)但是根本\(hack\)不掉,我也是挺迷的

解法倒是没啥大问题

\(f_i\)表示从\(i\)号点走到\(n\)号点,之前没有封过道路的最短距离

\(A\)\(i\)号点,考虑\((i,j)\)这条边,如果\(B\)封路,那么处理出\(i\)不走这条边到达\(n\)的最短路\(dv_{i-j}\)来更新答案,如果不封路,那么就是\(f_j+val_{i->j}\)更新答案,因为\(B\)采取最优策略所以取\(max\)

\(max(f_j+val_{i->j},dv_{i-j})->f_i\)

这是带环的\(DP\),使用\(Dijkstra\)计算答案

如何求解\(d_{i-j}\)

最短路径树的思想结合并查集进行解决

具体方法如下:

\(𝑛\)为原点,进行一次最短路算法,并且在每个点上记录,从\(𝑛\)到这个点的最
短路径的前驱,前驱有多个的话记录任意一个即可

构造一张新图,每个点与自己记录的前驱,边的长度为在原图中这两个点之间的边的长度

这样的连边过后会构造出一棵以点𝑛为根的有根树,被称为\(最短路径树\)

最短路径树具有很多优秀的性质,其中最重要的一条性质是:树上的两个点如果具有父子关系,则树上这两个点之间的路径对应于原图中这两个点之间的一条最短路

在求解\(𝑑 _{𝑖-j}\) 时,只有边 \(𝑖,𝑗\) 出现在树上的时候,这样的求解才有
意义。否则\(𝑑 _{𝑖-𝑗}\) 就等于原图中\(𝑖\)\(𝑛\)的最短路长度

所以,余下的任务是:对树上的每条边,求出它被删掉后,从点𝑛到这条边连接着的儿子的最短路长度

考虑删掉的边是 \(𝑢,𝑣\) 其中\(𝑢\)为父亲,\(𝑣\)为儿子。在 \(𝑢<->𝑣\) 被删掉以后,树被分为了两个连通块,一个是以𝑣为根的子树,另一个是剩余的部分

如果想从点\(𝑛\)移动到点\(𝑣\),一定会经过一条跨越了两个连通块的边

设这条边连接着第一个连通块内的点\(𝑎\),和第二个连通块的点\(𝑏\)

那么,就有一条从点\(𝑛\)移动到点\(𝑣\)的路径\(𝑛 − 𝑏 − 𝑎 − 𝑣\)

路径的长度是\(𝑑𝑖𝑠_𝑏 + 𝑙 _{𝑎-𝑏} + 𝑑𝑖𝑠_𝑎 − 𝑑𝑖𝑠_𝑣 = 𝑑𝑖𝑠_b+ 𝑑𝑖𝑠 _𝑎 + 𝑙 _{𝑎-𝑏} − 𝑑𝑖𝑠_𝑣\)

其中\(𝑑𝑖𝑠_𝑢\) 表示点\(𝑛\)到点\(𝑢\)的最短路长度

可以发现,上面的表达式中,前面几项和边 𝑎,𝑏 相关,而最后一项和\(𝑣\)有关。
\(𝑑𝑖𝑠 𝑏 + 𝑑𝑖𝑠 𝑎 + 𝑙 𝑎,𝑏 = 𝑝(𝑎,𝑏)\)

则在枚举了点𝑣后,我们的标是找到一条不在树上的边\((𝑎,𝑏)\)使\(𝑝(𝑎,𝑏)\)最小,且满足\(𝑎\)\(𝑣\)的子树中而\(𝑏\)不在

转变一个思路来求解上述问题。枚举边\((𝑎,𝑏)\),再考虑哪些点\(𝑣\)是可以选择的

可以发现,可行的点\(𝑣\)应该在\(𝑎 − 𝑏\)的树链上,且不能是\(𝑎\)\(𝑏\)\(𝑙𝑐𝑎\)

如果暴力地进行上述操作,时间复杂度不能接受(实际上能过)

如果我们把边按\(𝑝(𝑎,𝑏)\)进行排序,按顺序对每条边的\(𝑎 − 𝑏\)树链上的点(不包括\(𝑙𝑐𝑎\))进行赋值,那么被赋值过一次的点就不需要再被赋值。

以此条件进行优化:设\(𝐹[𝑢]\)表示\(𝑢\)\(𝑢\)的祖先中,深度最大的还没被赋值过的点。每次点\(𝑢\)被赋值后,就执行\(𝐹 𝑢 =𝑓𝑎[𝑢]\)

此时的\(𝐹[𝑢]\)并不是实时更新的,而是在我们需要获取\(𝐹[𝑢]\)的准确值时,不
断查看\(𝐹 [𝑢] ,𝐹 [𝐹 [𝑢]] ,𝐹 [𝐹 [𝐹 [𝑢]]]\),...,直到找到\(𝐹[ 𝑣 ]= 𝑣\)这玩意就跟并查集路径压缩一个道理

code
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=100005;
const int maxm=200005;
const ll inf=0x3f3f3f3f3f3f3f3f;
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
struct fsb{int a,b;ll val;}fb[maxm<<2|1];
struct edge{int val,net,to;}e[maxm<<1|1];
struct ed{int net,to;}ee[maxn];
bool cmp(fsb x,fsb y){return x.val<y.val;}
int n,m,cnt,rem[maxn],head[maxn],tot=1,he[maxn],tott,f[maxn],fa[maxn][19],dep[maxn];
ll dv[maxn],d[maxn],ans[maxn];
bool vis[maxn],is[maxm<<1|1];
void add(int u,int v,int w){
    e[++tot].net=head[u];head[u]=tot;
    e[tot].val=w;e[tot].to=v;
}
void ad(int u,int v){
    ee[++tott].net=he[u];
    he[u]=tott;ee[tott].to=v;
}
void dfs(int x){
    for(int i=1;i<=18;++i)fa[x][i]=fa[fa[x][i-1]][i-1];
    dep[x]=dep[fa[x][0]]+1;
    for(int i=he[x];i;i=ee[i].net){
        fa[ee[i].to][0]=x;
        dfs(ee[i].to);
    }
}
int LCA(int u,int v){
    if(dep[u]<dep[v])swap(u,v);
    int ddep=dep[u]-dep[v];
    for(int i=18;i>=0;--i)if((1<<i)<=ddep)ddep-=(1<<i),u=fa[u][i];
    if(u==v)return u;
    for(int i=18;i>=0;--i)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
int gf(int x){
    if(f[x]==x)return x;
    return f[x]=gf(f[x]);
}
void dij(){
    d[n]=0;q.emplace(0,n);
    while(!q.empty()){
        int x=q.top().second;q.pop();
        if(vis[x])continue;
        for(int i=head[x];i;i=e[i].net){
            int v=e[i].to;
            if(d[v]>d[x]+e[i].val){
                d[v]=d[x]+e[i].val;
                rem[v]=i;
                q.emplace(d[v],v);
            }
        }
    }
}
void DIJ(){
    memset(vis,0,sizeof(vis));
    ans[n]=0;q.emplace(0,n);
    while(!q.empty()){
        int x=q.top().second;q.pop();
        if(vis[x])continue;vis[x]=1;
        for(int i=head[x];i;i=e[i].net){
            int v=e[i].to;
            //if(ans[v]>max(ans[x]+e[i].val,is[i]?dv[v]:d[v])){
            if(ans[v]>max(ans[x]+e[i].val,dv[v])){
            //上边两个if都能AC,而且对拍拍不出错误,不是很理解,下面的这个显然是错误的,为什么答案是对的?
            //是因为非树边一定不会更新答案吗?但是直接不考虑非树边也是错的
            //更新出了非法状态但是得到了正确解???
                ans[v]=max(ans[x]+e[i].val,dv[v]);
                q.emplace(ans[v],v);
            }
        }
    }
}
int main()
{
    freopen("city.in","r",stdin);
    freopen("city.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;++i)d[i]=inf;
    for(int i=0;i<=n;++i)ans[i]=inf;
    for(int i=0;i<=n;++i)dv[i]=inf;
    for(int i=1;i<=m;++i){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    dij();
    for(int i=1;i<n;++i){
        ad(e[rem[i]^1].to,i);
        is[rem[i]]=is[rem[i]^1]=1;
    }
    dfs(n);
    for(int i=1;i<=n;++i)f[i]=i;
    for(int i=2;i<=tot;i+=2){
        if(is[i])continue;
        int u=e[i^1].to,v=e[i].to;
        fb[++cnt].val=e[i].val+d[u]+d[v];
        fb[cnt].a=u;fb[cnt].b=v;
    }
    sort(fb+1,fb+cnt+1,cmp);
    for(int i=1;i<=cnt;++i){
        int lca=LCA(fb[i].a,fb[i].b);
        int x=gf(fb[i].a);
        while(dep[x]>dep[lca]){
            dv[x]=fb[i].val-d[x];
            f[x]=fa[x][0];
            x=gf(x);
        }
        x=gf(fb[i].b);
        while(dep[x]>dep[lca]){
            dv[x]=fb[i].val-d[x];
            f[x]=fa[x][0];
            x=gf(x);
        }
    }
    DIJ();
    if(ans[1]==ans[0])printf("-1\n");
    else printf("%lld\n",ans[1]);
    return 0;
}

简单计算

数论牛逼题。。。

题解几行,完了。。。。。然而他确实就这么简单,但是确实难想(还是我太菜)

\(2\sum_{i=0}^p \lfloor iq/p\rfloor=\sum_{i=0}^p \lfloor iq/p\rfloor+\lfloor (p-i)q/p\rfloor\)

\(=(p+1)*q-\sum_{i=0}^p [(p|qi)?0:1]\)

\(=(p+1)*q-p+gcd(p,q)\)

最后一步简单说一下

\(p_1=p/gcd,q_1=q/gcd\)

\(p_1|q_1i\)

\(p_1|i\)

那么有\(p/p_1 =gcd\)个满足条件的

code
#include<cstdio>
#include<cstring>

using namespace std;

typedef long long ll;
ll p,q;

ll gcd(ll x,ll y){
    return y==0?x:gcd(y,x%y);
}

int main()
{
    freopen("simplecalc.in","r",stdin);
    freopen("simplecalc.out","w",stdout);
    int T;scanf("%d",&T);
    for(int ask=1;ask<=T;++ask){
        scanf("%lld%lld",&p,&q);
        printf("%lld\n",((p+1)*q-p+gcd(p,q))/2);
    }
    return 0;
}
posted @ 2022-02-20 21:39  Chen_jr  阅读(57)  评论(0编辑  收藏  举报