暑期集训3

rank 19 45分

题纲:T1:数论,欧几里得算法求解线性同余;

T2:线段树DP+贪心确定决策顺序;

T3:Dij神仙最短路;

T4:暴力+思维(细节太多,真的是没有20组以上数据可以调的出来的吗?)

T1:给你n个数,和正整数a,b,可以对每个数进行+a,-a,+b,-b操作,求最小操作次数之和。

每个数独立求解。假设+a-a进行x步,+b-b进行y步。ax+by=tmp的一组使得abs(x)+abs(y)最小的解决。先求ax+by=gcd(a,b)的一组解。然后对于ax+by=tmp,我们直接让(x,y)的答案/gcdtmp就行。对于(x,y)满足要求的解就是对于x同余于b/gcd,对于y是a/gcd,但是必须要求同步。但是可以先求出x的最小解,然后算出y,y=(tmp-ax)/b,x在求出来之后要调成最小的,直接%上就行。
然后对于答案的计算,我们默认以x为基准,算出x的一个最优解然后用x求出y,但是要注意,如果以x为基准,调整x是最小的正数或者是最大的负数,保证答案是对的,必须是让b>a,这样x的变化幅度>y,贪心的策略才正确。

点击查看代码
inline ll re(){
	ll x=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')h=-1;ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		x=(x<<1)+(x<<3)+(ch^48);ch=getchar();
	}
	return x*h;
}
#define int long long
int n,a,b,p,q,x,y,tmp,ans;
int exgcd(int a,int b){
	if(b==0){
		x=1,y=0;return a;
	}
	int bit=exgcd(b,a%b),t=x;
	x=y;y=t-a/b*x;return bit;
}
signed main()
{
	//freopen("","r",stdin);
	//freopen("","w",stdout);
	n=re(),a=re(),b=re();
	if(a>b)swap(a,b);//让a小
	int gcd=exgcd(a,b);
	a/=gcd,b/=gcd;
	x=(x%b);//x就会是偏大的一个数
	_f(i,1,n){
		tmp=re();if(tmp<=0)tmp=-tmp;
		if(tmp%gcd){
			chu("-1");return 0;
		}
		tmp/=gcd;
		 p=(x*tmp%b),q=(tmp-a*p)/b;
     // ans+=min(min(abs(p)+abs(q),min(abs(p-b)+abs(q+a),abs(p+b)+abs(q-a))));
	  int ok=min(abs(p)+abs(q),abs(p-b)+abs(q+a));
	 ok=min(ok,abs(p+b)+abs(q-a));
	  ans+=ok;
	}
	chu("%lld",ans);
	return 0;
}

T2:线段树优化DP

给你三元组(ai,bi,wi),要求你给出一种排列顺序,对于任意的i<j,ai<=bj,而且被选集合的wi之和最大。(n<=le5)

最优解的实现有3个步骤难点
(1)排列顺序:我们假设一种情况,如果i和j都要选,那么i必须在j前面的限制是什么,即ai<=bj,aj>bi,化简合并,体现在结构体的排序里就是
ai+bi<ai+bj
(2)选择:dp[i][j]表示前i个数里max_a=j的最多价值,
枚举到一个i,当bi>ai,只能从dp[i-1][1~ai]转移(第二维限制);
当ai>=bi,从dp[i-1][1~bi]转移(条件);
可以更新的决策集合:当ai<bi,可以更新dp[i][ai~bi]
当ai>bi,只能更新dp[i]][ai](用1~bi的)。发现都是区间上的操作,用线段树维护。
支持区间加减,维护单点最值。

我赛时也想到了DP,但是第一是没有想到ai+bi<aj+bj的排序,二是DP也转移的不对,不严谨,我定义的是dp[i][j]:最大值在1~j之间的一个最大价值
其实也没有影响,但是就是不对啊,虽然这么干好像就省略了一维枚举(到时候再想想爸)

点击查看代码
const int N=1e5+100;
struct node{
    int ai,bi;ll wi;
    bool operator<(const node&A)const{
       // if((ai+bi)==(A.ai+A.bi))return ai<A.ai;//why?
        return (ai+bi)<(A.ai+A.bi);
    }
}e[N];
int n,mp[N<<1],uni;
struct Segmentree{
    int ls,rs;ll mxk,tag;
}t[N<<2];int root,tot;
#define lson t[rt].ls
#define rson t[rt].rs
inline void Pushdown(int rt){
    if(!t[rt].ls)t[rt].ls=++tot;
    if(!t[rt].rs)t[rt].rs=++tot;
    if(t[rt].tag){
        t[lson].tag+=t[rt].tag;
        t[lson].mxk+=t[rt].tag;
        t[rson].tag+=t[rt].tag;
        t[rson].mxk+=t[rt].tag;
        t[rt].tag=0;
    }//区间加减和单点最大值的维护
}
inline void Pushup(int rt){
    t[rt].mxk=max(t[lson].mxk,t[rson].mxk);//一个区间维护的是这个区间的单点的最值
}
inline void Update(int&rt,int l,int r,int L,int R,ll vl){
    if(!rt)rt=++tot;
    if(L<=l&&r<=R){
        t[rt].mxk+=vl;t[rt].tag+=vl;return;
    }
    Pushdown(rt);
    int  mid=(l+r)>>1;
    if(L<=mid)Update(lson,l,mid,L,R,vl);
    if(R>mid)Update(rson,mid+1,r,L,R,vl);//维护了区间的最值
    Pushup(rt);
}
inline ll Query(int rt,int l,int r,int L,int R){
    if(!rt)return 0;
    if(L<=l&&r<=R){
        return t[rt].mxk;
    }
    Pushdown(rt);
    ll ans=0;
    int mid=(l+r)>>1;
    if(L<=mid)ans=max(ans,Query(lson,l,mid,L,R));
    if(R>mid)ans=max(ans,Query(rson,mid+1,r,L,R));
    return ans;
}
int main()
{
	//freopen("distance.in","r",stdin);
	//freopen("","w",stdout);
    n=re();
    _f(i,1,n){
        e[i].ai=re(),e[i].bi=re(),e[i].wi=re();mp[(i<<1)-1]=e[i].ai,mp[i<<1]=e[i].bi;
    }
    sort(e+1,e+1+n);
    sort(mp+1,mp+1+(n<<1));
    uni=unique(mp+1,mp+1+(n<<1))-mp-1;
    _f(i,1,n){
        int ar=lower_bound(mp+1,mp+1+uni,e[i].ai)-mp;
        int br=lower_bound(mp+1,mp+1+uni,e[i].bi)-mp;
        int mi=min(ar,br);
        ll t1=Query(root,1,uni,1,mi);
        ll t2=Query(root,1,uni,ar,ar);
        if(ar<br)Update(root,1,uni,ar+1,br,e[i].wi);//区间加
        Update(root,1,uni,ar,ar,-t2);
        Update(root,1,uni,ar,ar,max(t2,t1+e[i].wi));
    }
    chu("%lld",Query(root,1,uni,1,uni));
	return 0;
}

T3:给你一个无向联通有边权图,给你k个关键点,求出每个关键点到其他距离最短的关键点的距离。

**以所有特殊点为起点跑多源最短路,并且记录每个点是由哪个源点拓展的。
然后枚举所有边,如果边的两端是由不同源点拓展的,就更新这两个点的答案。
不难证明,对于源点 i,由 i 拓展的点 j 以及与 j 相邻且不由 i 拓展的点 k,
如果 i 的最优路径从 j 走到了 k,那么走到拓展 k 的源点是最优的。因此这个做
法是正确的。
比较关键的思路就是考虑到了非关键点这个媒介(1)你可以在spfa的时候不清空dis数组,正常更新,如果dis[u]<dis[top]+w,显然对于u节点来说,经过他从其他关键点就不更新比较优,最后枚举边就行(2)对于每个点,都维护一个最短距离和次短距离(起点来自于关键点),先把关键点放进去跑,然后去更新非关键点,当一个关键点被第二次跟新到,就是有了次短距离,就是答案(次短的概念是为了避免重复走同一个关键点)

点击查看代码
//#define int ll 
const int N=2e5+100;
struct node{
  int fr,to,nxt;ll w;
}e[N<<1];
int n,m,p,head[N],tot,sp[N];
inline void Add(int fr,int to,ll wi){
  e[++tot].nxt=head[fr],head[fr]=tot;e[tot].to=to;e[tot].w=wi;e[tot].fr=fr;
}
int belong[N];ll dis[N];
struct SAT{
  int id,bl;ll ds;
  SAT(){}
  SAT(int a,int b,ll c){
    id=a,bl=b,ds=c;
  }
  bool operator<(const SAT &A)const{
    return ds>A.ds;//最短距离
  }
};
priority_queue< SAT >st;bool vis[N];
inline void Dij(){
  _f(i,1,n)dis[i]=1e17;
  _f(i,1,p){
    belong[sp[i]]=sp[i];
    st.push(SAT(sp[i],sp[i],0));dis[sp[i]]=0;
  }
  while(!st.empty()){
    SAT op=st.top();
    vis[op.id]=1;
    for(rint i=head[op.id];i;i=e[i].nxt){
      int to=e[i].to;
      if(e[i].w+dis[op.id]<dis[to]){
        dis[to]=e[i].w+dis[op.id];
        belong[to]=op.bl;
      //  chu("new element:%d  belong:%d  dis:%lld\n",to,belong[to],dis[to]);
        st.push(SAT(to,belong[to],dis[to]));
      }
    }
    while(!st.empty()&&vis[st.top().id])st.pop();
  }
}
ll ans[N];
signed main()
{
	//freopen("distance.in","r",stdin);
	//freopen("","w",stdout);
  n=re(),m=re(),p=re();
  _f(i,1,p)sp[i]=re();
  _f(i,1,m){
    int u=re(),v=re(),w=re();
    Add(u,v,w);Add(v,u,w);
  }
  _f(i,1,n)ans[i]=1e17;
  Dij();
  for(rint i=1;i<=tot;i+=2){
    int fr=e[i].fr,to=e[i].to;
   // chu("try:%d--%d\n",fr,to);
    if(belong[fr]!=belong[to]){
      //chu("update:%d  and  %d\n",belong[fr],belong[to]);
      ans[belong[fr]]=min(ans[belong[fr]],e[i].w+dis[fr]+dis[to]);
      ans[belong[to]]=min(ans[belong[to]],e[i].w+dis[fr]+dis[to]);
    }
  }
 //  _f(i,1,n)chu("%lld ",ans[i]);
  // chu("\n");
  _f(i,1,p){
    chu("%lld ",ans[sp[i]]);
  }
	return 0;
}
struct node{
    int val, fro, to;
    node(){}
    node(int a, int c, int b){fro = a, to = c, val = b;}
    bool operator < (const node& a)const{
        return val > a.val;
    }
};
std::priority_queue<node> que;
int sp[maxn], vis[maxn], ans[maxn];
struct NODE{
    int fro1, val1;
    int fro2, val2;
}dis[maxn];
int n, m, p, x, y, z;
int main(){
    scanf("%d%d%d", &n, &m, &p);
    for(int i = 1; i <= p; ++i){
        scanf("%d", &x);
        sp[x] = i;
        dis[x].fro1 = x;
        //dis[x]:最短距离,最短距离来自哪个关键点;次短距离,次短距离来自哪
        que.push((node){x, x, 0});
    }
    for(int i = 1; i <= m; ++i){
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z), add(y, x, z);
    }
    while(!que.empty()){
    int u = que.top().to, fro = que.top().fro,
     u_w = que.top().val; que.pop();
        if(vis[u] == -1 || vis[u] == fro) continue;
        //如果已经更新了两次或者最短路已经是由fro更新的了,就跳过
        if(sp[u] && fro != u){
            //如果u是一个特殊点,_短路不是由自己更新的,当前已经是第二次更新
            //fro由是由另一个关键点来的,那么就可以终止了
            //special-->special完美
            vis[u] = -1;
            ans[sp[u]] = u_w;//当前就是答案
            continue;
        }
        if(vis[u]) vis[u] = -1;
        else vis[u] = fro;
        for(int i = head[u]; i; i = nex[i]){
            int v = to[i], w = val[i];
            if(!dis[v].fro1 || dis[v].val1 > u_w + w){//尝试更新最短路
                if(dis[v].fro1 != fro){
                    dis[v].fro2 = dis[v].fro1;
                    dis[v].val2 = dis[v].val1;
                }
                dis[v].fro1 = fro;
                dis[v].val1 = u_w + w;
                que.push((node){fro, v, u_w + w});
            }else if(!dis[v].fro2 || dis[v].val2 > u_w + w){
                if(dis[v].fro1 == fro) continue;
                dis[v].fro2 = fro;
                dis[v].val2 = u_w + w;
                que.push((node){fro, v, u_w + w});
            }
        }
    }
    for(int i = 1; i <= p; ++i) printf("%d ", ans[i]);

    return 0;
}

T4:n个人构成一个环,如果i说1,则是i后的人说真话,说0,是i后的人说假话,说$ k,是有k个人说真话。问是否存在矛盾.(n<=1e5)

image

点击查看代码
char s[2];
bool tak[100000+100];//如果他说了B话,1:后面是真;0:假
int pre[100000+100],id[100000+10],ct;//id[x]:第x个说A的人,是谁pre[id[x]]:预测是几个
int low[100000+100],lw_ct;
int cnt[100000+10],len[100000+10],n;
int huan[100000+10],h_ct;
inline bool deal(){
  _f(i,1,n)tak[i]=pre[i]=id[i]=ct=low[i]=lw_ct=cnt[i]=len[i]=h_ct=0;
  n=re();ct=0;
  _f(i,1,n)
  {
    scanf("%s",s);
    if(s[0]=='+')tak[i]=1;
    else  if(s[0]=='$'){
      int k=re();id[++ct]=i;pre[id[ct]]=k;low[ct]=k;
    }
    else
    {
      tak[i]=0;
    }
  }
  if(!ct){
   // chu("in>\n");
    int lst=1;//假设1说了真话
    _f(i,2,n){
      if(lst==1){//上个人说真话
        if(tak[i-1])//说i说了真话
        lst=1;
        else lst=0;//说i说了假话
      }
      else {
        if(tak[i-1])lst=0;
        else lst=1;
      }
    }
    //chu("lst:%d  tak[%d]:%d\n",lst,n,tak[n]);
    if(lst==1){
      if(!tak[n])return 0;
      return 1;
    }
    if(!tak[n])return 1;
    return 0;
  }//chu("out\n");
  //chu("ct:%d\n",ct);
  int sj=0;
  if(id[ct]!=n)
  {
    int now=id[ct]+1;
    while(1){
      huan[++h_ct]=now;++now;
      if(now>n)break;
    }
  }
  _f(i,1,id[1])huan[++h_ct]=i;//对于环结尾的特殊情况
  int lent=h_ct;
  int lst=1;int cot=0;
 //chu("h_ct:%d\n",h_ct);
  f_(j,h_ct-1,1){
    if(tak[huan[j]]&&lst)++cot;
    else if((!tak[huan[j]])&&(!lst))++cot,lst=1;
    else lst=0;
  }
  cnt[pre[id[1]]]+=cot+1;len[pre[id[1]]]+=lent;sj+=cot+1;
  //chu("len:%d  cnt:%d\n",lent,cnt[pre[id[1]]]);
 // chu("huan:cnt:%d  fcnt:%d\n",cnt[pre[id[1]]],fcnt[pre[id[1]]]);
  //fcnt:是i说假话的时候有几个人说假话
  //sj:是i说假话
  //chu("cot:%d\n",cot);
  _f(i,2,ct)//处理i-1+1~i位置,1的单独搞
  {
    int lent=id[i]-id[i-1];//链长度
    //假设id[i]说了真话
    int lst=1;int cot=0;
    f_(j,id[i]-1,id[i-1]+1){
      if(tak[j]&&lst)//如果j说lst说了真话
      {++cot;lst=1;}
      else if((!tak[j])&&(!lst)){++cot;lst=1;}
      else lst=0;
    }
    //chu("cnt[%d]+=%d fcnt[%d]+=%d\n",pre[id[i]],cot,pre[id[i]],len-cot);
    cnt[pre[id[i]]]+=cot+1;len[pre[id[i]]]+=lent;sj+=(cot+1);
  }
  //_f(i,0,n)chu("cnt[%d]:%d\n",i,cnt[i]);
  sort(low+1,low+1+ct);
  lw_ct=unique(low+1,low+1+ct)-low-1;
  _f(i,1,lw_ct)//枚举预测多少人说了真话
  {
   // if(cnt[low[i]]==low[i])
    if(cnt[low[i]]+(n-len[low[i]])-(sj-cnt[low[i]]) ==low[i])
    return 1;
  }
  //chu("sj:%d\n",sj);
 //假设全部说假话,sj个人说了假话
  _f(i,1,lw_ct)if(n-sj==low[i])return 0;
  //如果所有A说的都是假话,sj:所有A都是假话,说假话的人个数,n-sj:说真话的人个数,不可以和任何真话组合重复
  return 1;
  //cnt[x]:认为有x人说了真话,则说真话的人个数
  //sum[]:一共有几种话
}
int main()
{
	//freopen("c.in","r",stdin);
	//freopen("c.out","w",stdout);
    int T=re();
    while(T--){
        if(!deal())chu("inconsistent\n");
        else chu("consistent\n");
    }
	return 0;
}
posted on 2022-08-15 19:59  HZOI-曹蓉  阅读(19)  评论(0编辑  收藏  举报