暑期集训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)
点击查看代码
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;
}