noip2012day2题解

T1 同余方程

恩。。。exgcd。。对于会的人来说这道题基本等于水题吧

#include<cstdio>
void exgcd(long long a,long long b,long long &x,long long &y)
{
	if(!b)
	{
		x=1;
		y=0;
		return;
	}
	else 
	{
		exgcd(b,a%b,y,x);
		y-=(a/b)*x;
	}
}
int main()
{
	long long n,m;
	std::scanf("%I64d%I64d",&n,&m);
	long long x,y;
	exgcd(n,m,x,y);
	long long ans=(x%m+m)%m;//确保一定是一个最小正整数 
	printf("%I64d\n",ans);
	return 0;
}

T2借教室

12年居然一天考了两道二分答案。。。。真的是很神奇啊

虽然正解是二分答案+前缀和差分,但是从体面上看来可以果断地线段树一波。

然而一般线段树地pushdown地常数巨大无比,所以说能拿到数据里1e5的70分已经是极限了。

所以我们可以使用线段树地标记永久化,只在区间完全重合的时候打上修改标记,然后不下放标记,而在查询的时候就顺带将每个位置标记上应该计算的直都算到下一层递归里,这样可以极大地优化线段树地常数,而且标记的删除非常方便(你要把一个区间改回去只需要把区间最外边地几个小区间的标记改为0就好了)

实测可以AC

(个人感觉,在考场上,对于这种一眼看出可以二分的题目,如果check操作明显且无问题,那么果断选择直接二分答案,但如果想不到地话,个人建议最好还是想一些优化后能拿到高分的拿手数据结构)

贴上代码

#include<cstdio>
#include<algorithm>
const int MAXN=1e6+5;
const int INF=1e9+7;
class Node 
{
    public:
        Node *ls;
        Node *rs;
        int minx;
        int tag;
        void update();
        void maintain();
}pool[MAXN],*tail=pool,*root,*zero;
int w[MAXN];
void Node::update(void)
{
    minx=std::min(minx,std::min(ls->minx-ls->tag,rs->minx-rs->tag));
}
void Node::maintain(void)
{
	minx=std::min(ls->minx,rs->minx);
}
namespace tree
{
    void init()
    {
        zero=++tail;
        zero->ls=zero->rs=zero;
        zero->tag=0;
        zero->minx=0;
    }
    Node *build(int l,int r)
    {
        Node *nd=++tail;
        if(l==r)
        {
            nd->minx=w[l];
            nd->ls=zero;
            nd->rs=zero;
            nd->tag=0;
            return nd;
        }
        else
        {
            int mid=(l+r)>>1;
            nd->ls=build(l,mid);
            nd->rs=build(mid+1,r);
            nd->maintain();
        }
        return nd;
    }
    void modify(Node *nd,int l,int r,int L,int R,int delta)
    {
        if(L==l&&R==r)
        {
            nd->tag+=delta;
            return;
        }
        int mid=(l+r)>>1;
        if(R<=mid)
        {
            modify(nd->ls,l,mid,L,R,delta);
        }
        else 
        if(mid<L)
        {
            modify(nd->rs,mid+1,r,L,R,delta);
        }
        else
        {
            modify(nd->ls,l,mid,L,mid,delta),modify(nd->rs,mid+1,r,mid+1,R,delta);
        }
        nd->update();
    }
}
int l[MAXN],r[MAXN],d[MAXN];
int main()
{
    int n,q;
    std::scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
    {
        std::scanf("%d",w+i);
    }
    tree::init();
    root=tree::build(1,n);
    for(int i=1;i<=q;i++)
    {
        std::scanf("%d%d%d",d+i,l+i,r+i);//这里可以在线输出的,主要是luogu的评测系统必须要读完以后再输出否则它会显示re 
    }
    for(int i=1;i<=q;i++)
    {
        tree::modify(root,1,n,l[i],r[i],d[i]);
        if(root->minx-root->tag<0)
        {
            std::printf("-1\n");
            std::printf("%d\n",i);
            return 0;
        }
    }
    std::printf("0\n");
    return 0;
}
/*
input
4 3
4354 891456 4413 1182
1 1 4 1
2 1 4

output
1181

4 3 
2 5 4 3 
2 1 3 
3 2 4 
4 2 4

-1
2
*/

T3 疫情控制

个人认为很多年来除了天天爱跑步和列队之外最难的题目之一,思维难度本来就很大,而且代码非常恶心,还容易写错(不能停留在根节点这一个点真的让人写得要死要活的)

一、二分

我们首先发现,这个最小值一定不能模拟(时间爆炸而且得不到最优解),所以我们首先想到二分可以达到的最小值

二、贪心

我们发现,将军队向上跳一定是最优,如果军队在根节点下面的某个节点的话,它能够控制的子节点一定是最多的

对于不能跳到根节点的军队,我们将它们记录下来,按照跳到根节点后的剩余时间排好序,从小到大依次对应还不能被控制的节点,如果存在某个节点,所有军队都控制不了,那么说明当前答案是不合法的

我们发现数据范围是5e4,所以说直接一步一步向上跳会T,因此我们需要使用倍增来优化时间复杂度,预处理出跳2^p的代价

用dfs找出还未被覆盖的节点,然后用记录下来的军队来匹配

总结是二分答案+贪心+倍增(挺神的一道题)

#include<cstdio>
#include<cstring>
#include<algorithm>
const int MAXN=5e4+5;
const int P=16+1;
struct Edge
{
    int nxt;
    int to;
    int w;
}edge[MAXN<<1];
int head[MAXN];
int num;
void add(int from,int to,int w)
{
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].w=w;
    head[from]=num;
}
int anc[MAXN][P+1];
long long wp[MAXN][P+1];
int dep[MAXN];
int army[MAXN];
struct Node
{
    int rest;
    int id;
}a[MAXN],b[MAXN];
int na;
int nb;
int n,m;
int root;
int rest_time[MAXN];//控制某一个点的军队 
int rest_army[MAXN];//控制某个点的军队的剩余时间(根据贪心策略,尽量使用剩余时间小的点来控制某个点,剩余时间多的则移动控制其他点) 
bool used[MAXN];//某个军队是否已经使用过 
bool vis[MAXN];//判断某个点以及其子树是否被控制 
void pre(int u)
{
    for(int p=1;p<=P;p++)
    {
        anc[u][p]=anc[anc[u][p-1]][p-1];
        wp[u][p]=wp[anc[u][p-1]][p-1]+wp[u][p-1];//某个位置条2^p步的花费 
    }
}
void dfs(int u,int f)
{
    for(int i=head[u];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(v==f)
        {
            continue;
        }
        anc[v][0]=u;
        wp[v][0]=edge[i].w;//走上一步的花费 
        pre(v);
        dfs(v,u);
    }
}
bool find(int u,int f)
{
    if(vis[u]) return 1;
    bool leaf=1;
    bool flag=1;
    for(int i=head[u];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(v==f) continue;
        leaf=0;
        if(!find(v,u))
        {
            flag=0;
            if(u==root)
            {
                b[++nb].rest=edge[i].w;//寻找不能被军队覆盖的与根节点直接相连的子节点 
                b[nb].id=v;
            }
            else 
            {
                return 0;
            }
        }
    }
    if(leaf) return 0;
    return flag;
}
bool cmp(const Node &a,const Node &b)//写不来重载运算符qwq 
{
    return a.rest>b.rest;
}
bool check(int mid)
{
    std::memset(a,0,sizeof(a));
    std::memset(b,0,sizeof(b));
    std::memset(rest_time,0,sizeof(rest_time));
    std::memset(rest_army,0,sizeof(rest_army));
    std::memset(vis,0,sizeof(vis));
    std::memset(used,0,sizeof(used));
    na=0;
    nb=0;
    for(int i=1;i<=m;i++)
    {
        int u=army[i];
        int t=0;
        for(int p=P;p>=0;p--)
        {
            if(anc[u][p]&&anc[u][p]!=root&&wp[u][p]+t<=mid)
            {
                t+=wp[u][p];
                u=anc[u][p];
            }
        }
        if(anc[u][0]==root&&wp[u][0]+t<=mid)
        {
            a[++na].rest=mid-(t+wp[u][0]);
            a[na].id=i;
            if(!rest_army[u]||a[na].rest<rest_time[u])
            {
                rest_army[u]=i;
                rest_time[u]=a[na].rest;//用剩余时间最小的点覆盖当前节点 
            }
        }
        else
        {
            vis[u]=1;
        }
    }
    if(find(1,0))
    {
        return 1;
    }
    int now=1;
    used[0]=1;
    std::sort(a+1,a+1+na,cmp);
    std::sort(b+1,b+1+nb,cmp);//排序差分,大对大,小对小 
    for(int i=1;i<=nb;i++)
    {
        if(!used[rest_army[b[i].id]])
        {
            used[rest_army[b[i].id]]=1;
            continue;
        }
        while(now<=na&&(used[a[now].id]||a[now].rest<b[i].rest))
        {
            ++now;
        }
        if(now>na)return 0;//如果所有军队的时间都不足以控制,则返回0 
        used[a[now].id]=1;//将已经用于控制某节点的军队打上标记 
    }
    return 1;
}
int main()
{
    root=1;
    std::scanf("%d",&n);
    for(int i=1;i<=n-1;i++)
    {
        int u,v,w;
        std::scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    std::scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        std::scanf("%d",army+i);
    }
    dfs(root,0);
    int l=0;
    int r=500000;
    int ans=-1;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid))
        {
            ans=mid;
            r=mid;//如果当前时间可行,说明最短时间可能会更短 
        }
        else 
        {
            l=mid+1;
        }
    }
    std::printf("%d\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值