UOJ Logo heqingyu的博客

博客

普及转提高第7场训练博客

2018-01-13 22:06:01 By heqingyu

这次总算是进前三了。。。。

然而原因竟是XMR和LKY爸爸都没交。。。

T1:

这是一道结论题。。。

每个数都可以被分解成若干个质数之积,因此只需要从1~n,寻找能否整除n,能的话直接除。。。

code:

#include<bits/stdc++.h>
using namespace std;
long long n,ans,p[1000010];
int main(){
    freopen("math.in","r",stdin);
    freopen("math.out","w",stdout);    
    scanf("%lld",&n);
    for(long long i=2;i<=n;i++)
        if(n%i==0){n/=i;break;}
    for(long long i=2;i<=n;i++)
        if(n%i==0){n/=i;ans=i;break;}
    printf("%lld",ans);
    return 0;
}

T2:

这道题目是一道纯暴力模拟题。。。

code:

#include<bits/stdc++.h>
using namespace std;
int n,ans;
struct node {
    char mp[15][15];
}a,b;
node solve2(node a){
    node f;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f.mp[j][n-i+1]=a.mp[i][j];
    return f;
}
node solve1(node a){
    node f;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f.mp[i][n-j+1]=a.mp[i][j];
    return f;
}
bool check(node a,node b){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(a.mp[i][j]!=b.mp[i][j])return false;
    return true;
}
int main(){
    freopen("transform.in","r",stdin);
    freopen("transform.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%s",b.mp[i]+1);
    for(int i=1;i<=n;i++) scanf("%s",a.mp[i]+1);
    if(check(a,solve2(b))) ans=1;
    else if(check(a,solve2(solve2(b)))) ans=2;
    else if(check(a,solve2(solve2(solve2(b))))) ans=3;
    else if(check(a,solve1(b))) ans=4;
    else if(check(a,solve2(solve1(b)))||check(a,solve2(solve2(solve1(b))))||check(a, solve2(solve2(solve2(b))))) ans=5;
    else if(check(a,b)) ans=6;
    else ans=7;
    printf("%d",ans);
    return 0;
}

T3:

这道题第一眼看到就明白是DFS。。。。

但是为了有趣(细节调不好),所以打表

code:

#include<bits/stdc++.h>
using namespace std;
int f4[110]={0,2333,2339,2393,2399,2939,3119,3137,3733,3739,3793,3797,5939,7193,7331,7333,7393,};
int f1[110]={0,2,3,5,7};
int f2[110]={0,23,29,31,37,53,59,71,73,79};
int f3[110]={0,233,239,293,311,313,317,373,379,593,599,719,733,739,797};
int f5[110]={0,23333,23339,23399,23993,29399,31193,31379,37337,37339,37397,59393,59399,71933,73331,73939};
int f6[110]={0,233993,239933,293999,373379,373393,593933,593993,719333,739391,739393,739397,739399,};
int f7[110]={0,2339933,2399333,2939999,3733799,5939333,7393913,7393931,7393933};
int f8[110]={0,23399339,29399999,37337999,59393339,73939133};
void work(int x){
    int i=0;
    if(x==1)while(f1[++i]) printf("%d\n",f1[i]); 
    if(x==2)while(f2[++i]) printf("%d\n",f2[i]);
    if(x==3)while(f3[++i]) printf("%d\n",f3[i]);
    if(x==4)while(f4[++i]) printf("%d\n",f4[i]);
    if(x==5)while(f5[++i]) printf("%d\n",f5[i]);
    if(x==6)while(f6[++i]) printf("%d\n",f6[i]);
    if(x==7)while(f7[++i]) printf("%d\n",f7[i]);
    if(x==8)while(f8[++i]) printf("%d\n",f8[i]);
    return ;
}
int main(){
    freopen("sprime.in","r",stdin);
    freopen("sprime.out","w",stdout);
    int n;
    scanf("%d",&n);
    work(n);
    return 0;
}

T4:

这是一道并查集的高级操作。。。

XRY大佬当场怒A(就当他A了。。。)好强!!

维护一个并查集,fa[i]是当前集合的爸爸(就像ZJH和XRY一样。。。)

然后再维护一个数组s[i]表示i这条边与他的爸爸是平行还是垂直

然后判断ORZK的话就可以把a,b还有p(操作序号)xor起来。。。

code:

#include<bits/stdc++.h>
using namespace std;
int n,m,a,b,p,fa[100010],s[100010],ans[100010];
int getfa(int x){
    if(fa[x]==x) return x;
    int t=getfa(fa[x]);    
    s[x]^=s[fa[x]];
    return fa[x]=t;
}
bool check(int x,int y,int w){
    int fx=getfa(x),fy=getfa(y);
    if(fx==fy) return s[x]^s[y]^w;
    fa[fx]=fy;
    s[fx]=s[x]^s[y]^w;
    return 0;
}
int main(){
    freopen("imo.in","r",stdin);
    freopen("imo.out","w",stdout);
    scanf("%d%d",&n,&m);int cnt=0;
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&p,&a,&b);
        if(p==3){
            int fx=getfa(a),fy=getfa(b);    
            if(fx!=fy) ans[++cnt]=2;
            else ans[++cnt]=s[a]^s[b];
        }
        else if(check(a,b,p-1)){puts("ORZKsister");return 0;}
    }
    for(int i=1;i<=cnt;i++){
        if(ans[i]==1) puts("J"); 
        else if(!ans[i]) puts("Z");
        else puts("K"); 
    }
    //cout<<cnt<<endl;
    return 0;
}

普及转提高训练赛第六场

2018-01-07 14:52:55 By heqingyu

这次比赛打GG了。。。

T1:

根据syf教练的说法,n,m<=100,就可以直接上n^3的DP了。。

设f[i]表示前i个灯泡的放置方案数,然后我们可以得出转移方程:

f[i+k]+=f[i],其中k是一个1~a[i]的数

首先把p和a存下来,再记录一下位置,即代码中的id

然后按照p排序一遍,然后枚举灯泡,灯座接口,还有1~a[i],进行转移

但是要记得判断p的位置有没有重叠,重叠的话就可以break了

枚举p数组需要从大到小,不然的话可能会导致重复使用灯泡

code:

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
int f[110],p[110],n,m;
struct node{
    int p,a,id; 
}a[110];
bool cmp(node a,node b){return a.p<b.p;}
int main(){
    freopen("physics.in","r",stdin);
    freopen("physics.out","w",stdout);    
    scanf("%d%d",&n,&m);   
    for(int i=1;i<=n;i++)scanf("%d%d",&a[i].p,&a[i].a),a[i].id=i;         
    for(int i=1;i<=m;i++)scanf("%d",&p[i]);    
    sort(a+1,a+n+1,cmp);   
    f[0]=1;    
    for(int i=1;i<=n;i++){    
        for(int j=m-1;j>=0;j--)            
            if(f[j]){            
                for(int ij=1;ij<=a[i].a;ij++){                
                    int k=j+ij;                    
                    if(k>m)    break;                            
                    if(p[k]&&p[k]!=a[i].id)    break;                    
                    f[k]+=f[j];                    
                    f[k]%=mod;                    
                }                
            }            
    }    
    printf("%d",f[m]==0?-1:f[m]);    
    return 0;    
}

T2:

这道题我用了10分钟就凑出来一段代码了。。

注意,是凑出来的。。。

首先仔细观察样例,找到这个轮换和置换的运算方法,然后暴力模拟。。

code

#include<bits/stdc++.h>
using namespace std;
int n,k,p,m,x,a[100010],b[100010];
vector<int> v[100010];
int main(){
    freopen("rotate.in","r",stdin);
    freopen("rotate.out","w",stdout);
    scanf("%d%d%d",&n,&p,&k);    
   for(int i=1;i<=p;i++){    
        scanf("%d",&m);        
        for(int j=1;j<=m;j++)scanf("%d",&x),v[i].push_back(x);        
    }    
    for(int i=1;i<=n;i++)a[i]=i,b[i]=i;    
    for(int i=p;i>=1;i--)    
        for(int j=(int)v[i].size()-1;j>=1;j--){        
            swap(a[b[v[i][j]]],a[b[v[i][j-1]]]);            
            swap(b[v[i][j]],b[v[i][j-1]]);            
        }        
    for(int i=1;i<=n;i++)printf("%d ",a[i]);    
    return 0;    
}

T3:

这道题因为没怎么细看,导致只打了一个骗分。。。

所以还是要好好看题。。

大概就是说暴力模拟竖式除法的过程,然后用一个桶f来记录一下每次的被除数之前是否出现

如果出现,就说明有循环

这题主要是考察细节。。。

code:

#include<bits/stdc++.h>
using namespace std;
int ans,n,d,tot,f[1000010],a[1000010],cnt;
char s[1000010];
void upd(int x){
    if(x<10) s[++cnt]=x+'0';
    else{
        upd(x/10);
        s[++cnt]=x%10+'0';
    }
    return ;
}
int main(){
    freopen("fracdec.in","r",stdin);
    freopen("fracdec.out","w",stdout);
    scanf("%d%d",&n,&d);
    if(n%d==0){printf("%d.0",n/d);return 0;}
    for(int i=1;i<=d;i++)f[i]=-1;
    upd(n/d);
    n%=d;
    f[n]=0; 
    s[++cnt]='.';
    while(1){
        a[++ans]=n*10/d;
        n=n*10%d;        
        if(n==0) break;    
        if(f[n]==-1) f[n]=ans;
        else break;
    }
    if(n==0) for(int i=1;i<=ans;i++) upd(a[i]);
    else{
        for(int i=1;i<=f[n];i++)    upd(a[i]);
        s[++cnt]='(';
        for(int i=f[n]+1;i<=ans;i++) upd(a[i]);
        s[++cnt]=')';
    }    
    for(int i=1;i<=cnt;i++){
        putchar(s[i]);
        if(i%76==0) puts("");
    }
    return 0;
}

T4:

这道题是最后才做的。。。

首先手动分析样例,发现当一条边的承重z小于当前货物w的重量时,是废的

因此只需要把这些边全部给清除掉就可以了,然后判断有几个联通块(用并查集来做)

但是由于时间比较紧,所以没能想到离线排序后做,所以只有30分。。

code:

#include<bits/stdc++.h>
using namespace std;
int n,m,q,x,y,z,fa[100010],ans;
int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
struct node{
    int x,y,z;
}a[100010];
struct e{
    int x,y;
}w[100010]; 
bool cmp(node x,node y){return x.z>y.z;}
bool cmp1(e x,e y){return x.x>y.x;}
bool cmp2(e x,e y){return x.y<y.y;}
int main(){
    freopen("warehouse.in","r",stdin);
    freopen("warehouse.out","w",stdout);
    scanf("%d%d%d",&n,&m,&q);
    ans=n;
    for(int i=1;i<=m;i++)scanf("%d%d%d",&x,&y,&z),a[i]=(node){x,y,z}; 
    for(int i=1;i<=q;i++)scanf("%d",&w[i].x),w[i].y=i;
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(w+1,w+q+1,cmp1);
    sort(a+1,a+m+1,cmp);
    int now=1;
    for(int i=1;i<=q;i++){
        while(a[now].z>=w[i].x){
            int fx=getfa(a[now].x),fy=getfa(a[now].y);
            if(fx!=fy) fa[fx]=fy,ans--;
            now++; 
        }
        w[i].x=ans;
    }
    sort(w+1,w+q+1,cmp2);
    for(int i=1;i<=q;i++)printf("%d\n",w[i].x);
    return 0;
}

T5:

首先得出一个结论:

1.数越多,and值单调递减

2.数越多,or值单调递增

然后就可以固定右端点r,二分出最大的满足条件的左端点l,再二分出最小的满足条件的l,相减+1即可得到答案(因为单调)

然后判断是否合法可以用st表或者线段树来实现,但是这题常数很大,推荐st表

然而我还是写了线段树。。。用了inline、read、print还有线段树区间l,r赋为全局变量来卡常,终于险A:

code:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7; 
int n,a,b,c,len,d,s[100010],l,r,MAPLE,l1,r1;
struct node{
    int cor,cand,lc,rc,l,r;
}tr[500010];
inline int ask_or(int now){
    if(tr[now].l>=l1&&tr[now].r<=r1) return tr[now].cor;
    int mid=tr[now].l+tr[now].r>>1;
    if(mid>=r1) return ask_or(tr[now].lc);
    else if(mid<l1) return ask_or(tr[now].rc);
    else return ask_or(tr[now].lc)|ask_or(tr[now].rc);
}
inline void build_tree(int l,int r){
    len++;int now=len;
    tr[now].l=l,tr[now].r=r;
    if(l==r){tr[now].cor=tr[now].cand=s[l];return ;}
    int mid=l+r>>1;
    tr[now].lc=len+1;build_tree(l,mid);
    tr[now].rc=len+1;build_tree(mid+1,r);
    tr[now].cand=tr[tr[now].lc].cand&tr[tr[now].rc].cand;
    tr[now].cor=tr[tr[now].lc].cor|tr[tr[now].rc].cor; 
    return ;
}
inline int ask_and(int now){
    if(tr[now].l>=l1&&tr[now].r<=r1) return tr[now].cand;
    int mid=tr[now].l+tr[now].r>>1;
    if(mid>=r1) return ask_and(tr[now].lc);
    else if(mid<l1) return ask_and(tr[now].rc);
    else return ask_and(tr[now].lc)&ask_and(tr[now].rc);
}
inline void read(int &x){
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    x=0;while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
}
void print(int x){
    if(x<10) putchar(x+'0');
    else{
        print(x/10);
        putchar(x%10+'0'); 
    }
} 
int main(){
    freopen("range.in","r",stdin);
    freopen("range.out","w",stdout);
    read(n);read(a);read(b);read(c);read(d);
    for(int i=1;i<=n;i++)read(s[i]);
    build_tree(1,n);
    for(int i=1;i<=n;i++){
        int r=i,l=1,ans=0; 
        while(l<=r){
            int mid=l+r>>1;
            l1=mid;r1=i; 
            int t=ask_and(1); 
            int k=ask_or(1);
            if(t<=b&&k>=c) l=mid+1,ans=mid;
            else r=mid-1;
        } 
        l=1; 
        int cnt=ans;ans=i+1; 
        r=i;
        while(l<=r){
            int mid=l+r>>1;
            l1=mid;
            r1=i;
            int t=ask_and(1);
            int k=ask_or(1);
            if(t>=a&&k<=d) r=mid-1,ans=mid;
            else l=mid+1;
        } 
        if(cnt-ans+1>0) MAPLE+=cnt-ans+1;
        MAPLE%=mod;
    }
    print(MAPLE%mod); 
    return 0;
}

T6:

专门为XMR爸爸准备的题啊。。。

我只理概念:

期望:

对于每种概率,乘上这种可能的值,他们的和就是期望

没了。。。

普及转提高训练赛第五场

2018-01-01 21:02:39 By heqingyu

这次终于没有垫底了。。。

本来是预计有280分的,但是第三题的DP写炸了,后来发现答案加一就可以拿到50分了。。。

T1:

因为是求最小步数,可以考虑用贪心或DP来解决,但是由于DP不会,所以就贪心吧。。。

贪心思想:

用桶记录下1的个数,2的个数,然后考虑要给1留多少空间,给2留多少空间,再进行贪心的交换

code:

scanf("%d",&n);

for(int i=1;i<=n;i++){

    scanf("%d",&a[i]);

    if(a[i]==1) b[1]++;

    if(a[i]==2) b[2]++;

}

for(int i=1;i<=b[1];i++)
    if(a[i]==3){

        for(int j=n;j>b[1];j--)

            if(a[j]==1){

                swap(a[i],a[j]);

                ans++;

                break;

            }

    }

    else if(a[i]==2){

        for(int j=b[1]+1;j<=n;j++)

            if(a[j]==1){

                swap(a[i],a[j]);

                ans++; 

                break;

            }

    }

for(int i=b[1]+1;i<=b[1]+b[2];i++)

    if(a[i]==3){

        for(int j=n;j>=b[1]+b[2]+1;j--)

            if(a[j]==2){

                swap(a[i],a[j]);

                ans++;

                break;

            }

    }

printf("%lld",ans);

T2:

这题主要的瓶颈在于掌握O(n log n)的艾式筛法或者O(n)的欧拉筛,求出1`n之间的所有质数后进行二分判断,更新答案

code:

for(int i=2;i<=1000000;i++){

    if(!h[i]) p[++m]=i;

     for(int j=1;j<=m&&p[j]*i<=1000000;j++){

         h[i*p[j]]=true;

         if(!i%p[j]) break;

    }

}

 for(int i=1;i<=m;i++) s[i]=s[i-1]+p[i];

 read(t);

 while(t--){

     read(n),read(k);

     l=1;r=m-k+2;ans=0;

     if(r<2){puts("-1");continue;}

     while(l<=r){

         int mid=l+r>>1;

         if(s[mid+k-1]-s[mid-1]<=n) l=mid+1,ans=mid;

         else r=mid-1;

    }

     if(s[ans+k-1]-s[ans-1]<=n&&ans) printf("%lld\n",s[ans+k-1]-s[ans-1]);

     else puts("-1");

}

T3:

这道题目初见以为是最长下降子序列,后来发现有c[i]值,可以用另外一种DP来解决。。。

骗分:

我也不知道这是怎么想到的。。

设f[i][j]为从i跳到j(有中转)的最多次数(漏洞百出。。。),c[i][j]为从i跳到j(有中转)的最小代价(符合f数组),

然后设g[i][j]为从i直接跳到j的代价,可以得出方程:

f[i][j]=max(f[i][j],f[i][x]+f[x+1][j]+1);

c[i][j]=min(g[i][x]+g[x+1][j]+g[x][x],c[i][j]);

scanf("%d",&n);

for(int i=1;i<=n;i++)scanf("%d",&a[i].c);

for(int i=1;i<=n;i++)scanf("%d",&a[i].h);

scanf("%d",&v);

int flag=1;

for(int i=1;i<=n;i++)if(a[i].h!=a[i+1].h) flag=0;

if(flag){

    sort(a+1,a+n+1,cmp);

    for(int i=1;i<=n;i++){

        v-=a[i].c;

        if(!v) break;

        ans++;

    }

    printf("%d",ans);

}

else{

    memset(c,127,sizeof(c));

    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)g[i][j]=a[i].c+i!=j?a[j].c:0+abs(a[i].h-a[j].h);

    for(int i=1;i<=n;i++)

        for(int j=i+1;j<=n;j++)

            for(int x=i;x<=j;x++){

                f[i][j]=max(f[i][j],f[i][x]+f[x+1][j]+1);

                c[i][j]=min(g[i][x]+g[x+1][j]+g[x][x],c[i][j]);

            }

    for(int i=1;i<=n;i++){

        for(int j=1;j<=n;j++)

            if(c[i][j]<=v) ans=max(ans,f[i][j]);

    }

    printf("%d",ans/2+1);

} 

然后问一下大佬:为什么这个DP是对的(虽然只有50分)。。

T4:

这道题本来应该是水题,结果由于数论太差了。。(辗转相减是什么都不知道。。)没做出来

由于辗转相减与辗转相除其实是一样的,所以其实可以用辗转相除法来求gcd来得到答案

code:

scanf("%lld%lld",&a,&b);

if(a<b) swap(a,b);

while(b){

    ans+=a/b;

    a%=b;

    swap(a,b);

} 

printf("%lld",ans+1);

T5:

这是一道模拟题,但是作为模拟的铺垫的思维比较繁琐,我很懒,就不想写算了。。。

T6:

这道题我只拿到了30分,剩下来的70分有30分是我这个水平的选手能拿到的。。。

这个30分就是说每次给出的模数p相同,那么可以用vector来预处理出每个a[i] mod p所得的数所处的位置(下标)

然后就可以开始二分,但是要注意二分不能二分整个区间,而是先二分出左边界(>=l)然后再二分右边界(<=r)就可以O(log n)求出答案

需要注意的是,由于vector可能为空(或者其他特殊情况),所以第一次二分ans=vector[v].size(),第二次ans=-1,这样使得区间最鬼畜化,就可以避免爆炸。。。

code:

int check1(int x){return ve[v][x]>=l;}

int check2(int x){return ve[v][x]<=r;}

scanf("%d%d",&n,&m);

for(int i=1;i<=n;i++)scanf("%d",&a[i]);

if(n<=1e3&&m<=1e3){

    while(m--){

        ans=0;

        scanf("%d%d%d%d",&l,&r,&p,&v);

        for(int i=l;i<=r;i++)if(a[i]%p==v) ans++;

        printf("%d\n",ans);

    }

    return 0;

}        

scanf("%d%d%d%d",&l,&r,&p,&v);

for(int i=1;i<=n;i++){

    a[i]%=p;

    ve[a[i]].push_back(i);

}

for(int i=l;i<=r;i++)if(a[i]==v) ans++;

printf("%d\n",ans); 

m--; 

while(m--){

    ans=0;                

    scanf("%d%d%d%d",&l,&r,&p,&v);

    int l1=0,r1=(int)ve[v].size()-1;ans=ve[v].size();

    while(l1<=r1){

        int mid=l1+r1>>1;

        if(check1(mid)) r1=mid-1,ans=mid;

        else l1=mid+1;

    }    

    int cnt=ans;ans=-1;

    l1=0;r1=(int)ve[v].size()-1;

    while(l1<=r1){

        int mid=l1+r1>>1;

        if(check2(mid)) l1=mid+1,ans=mid;

        else r1=mid-1;

    }

    printf("%d\n",ans-cnt+1);

}

完结。。。

最后问大佬一个问题:

为什么t4的鬼畜DP能拿50分啊。。。

而且听说zjh大佬用我的DP方程做到了AC。。。我也是颜面扫地啊。。。我提出的算法,别人A了,我没有。。。

树链剖分的基本思想

2017-12-31 22:36:32 By heqingyu

一、树链剖分的作用

  通常是求树上u到v的路径节点之和

  这个问题很容易可以想到设f[i]表示根节点到i节点的节点之和

  t=lca(u,v),然后可以把u->v划分为u->t+t->v-t;

  则有结论:u->v=f[u]+f[v]-2*f[t]+t,这样就可以求出来了

  但是加上u到v的路径上的节点值的修改的话,就必须用树链剖分了。。。

二、几个概念:

  1.重儿子:一个节点的所有儿子中子树最大的那一个(相同任意取一个)

  2.轻儿子:除了重儿子以外的所有其他儿子

  3.重边:father连向重儿子的边

  4.轻边:除了重边以外的边

  5.重链:一条路径,上面全部都是由重儿子构成(即全为重边)

三、几个推论:

  1.两条重链之间必然有一些轻边连接

  2.我们用一条重链的起始节点来表示一条重链(每个节点只会属于一条重链)

  3.bel[x]表示x节点的重链的开头节点

四、问题的解决:

  首先对于修改操作,将lca(u,v)求出,然后判断bel[x]是否等于bel[y]

  1.如果等于,那就可以直接对一条重链上的节点进行修改

  2.对于每条重链来维护一段区间信息,然后如果不同的话,就可以不断地跳,直到跳到同一条重链上

  3.考虑每个节点都肯定包含在一条重链中,因此为每条重链建一棵线段树,然后就可以实现区间查询和区间修改

求LCA的倍增算法

2017-12-31 22:12:05 By heqingyu

一、LCA的定义:

  在一棵树上,点u到点v之间的路径最短的那个节点就是lca(u,v)

二、倍增思想:

  我们定义fa[i][j]表示节点i往上跳跃2^j次所到达的节点标号,则有结论:

  1.因为2^(j-1)+2^(j-1)=2^j,所以fa[i][j]=f[f[i][j-1]][j-1],即2^j的父亲就是跳2^(j-1)步到达的点再跳2^(j-1)所到达的点;

  2.dep[i]表示节点i的深度,可以一次dfs求出

  3.fa[i][0]=fa[i];

三、具体运用:

  若dep[u]!=dep[v],则把dep大的往上跳k=dep[x(较大)]-dep[y(较小)]步,使得两个节点处于同一层上

  可以把k表示为2的幂次和,即转换为二进制然后按位做,这时候fa数组就派上用场了

  往上跳k步可以表示为多个fa数组中的j之和,求出跳越k层后的节点

  然后两个节点一起往上走,直到遇见

较高级的线段树

2017-12-31 21:59:55 By heqingyu

一、动态开点线段树

为什么要搞这样的一个鬼畜线段树?

  1.区间范围过大,但是只会用到一些特定的节点,暴力开会爆内存

  2.要开多棵线段树,考虑会内存爆炸,也只建需要用的节点

这个线段树的唯一区别就是需要记录下左儿子和右儿子的编号

二、主席树(可持久化线段树)

First:

  主席树的来源:

  一个名叫主席的神犇发明的数据结构。。。

Second:

  主席树的作用:

  可以实现查询单调递增的序列的特征值,拥有继承的能力,但是不能支持修改操作。。。

Third:

  主席树的思想:

  适用于区间线段树大部分相同的情况下,由于只有少数节点不同,

  所以可以从前面的线段树继承过来,只需要添加节点

普及转提高模拟赛第四场

2017-12-26 16:44:26 By heqingyu

这次是我打的最伤心的一次。。。

本来预计有300的,结果错误百出,主要有以下几点:

1.freopen注释掉了

2.using namespace std多加了一个D

总之来说就是比赛经验太少了。。。

T1:

贪心,比较水的题,大概就是sort一遍,然后取奇数位上的数

code:

scanf("%d",&n);

n*=2; 

for(int i=1;i<=n;i++)scanf("%d",&a[i]);

sort(a+1,a+n+1);

for(int i=1;i<=n;i+=2)ans+=a[i];

printf("%lld",ans);

T2:

这是一道最小生成树的模板题,prim,kruskal都可以

code:

int n,len,fa[10010],x,t;

long long ans;

struct node{

int x,y,z;

}v[10010];

int getfa(int x){

if(fa[x]==x) return x;

else{

    fa[x]=getfa(fa[x]);

    return fa[x];

}

}

bool cmp(node x,node y){

return x.z<y.z;

}

int main(){

freopen("net.in","r",stdin);

freopen("net.out","w",stdout);

scanf("%d",&n);

for(int i=1;i<=n;i++)

    for(int j=1;j<=n;j++)

        scanf("%d",&x),v[++len]=(node){i,j,x};

sort(v+1,v+len+1,cmp);

for(int i=1;i<=n;i++)fa[i]=i;

for(int i=1;i<=len;i++){

    if(t==n-1) break;

    int fx=getfa(v[i].x),fy=getfa(v[i].y);

    if(fx!=fy){t++;ans+=v[i].z;fa[fx]=fy;} 

}

printf("%lld",ans);

return 0;

}

T3:

这道题目的确比较考思维,可惜我只想到了树的做法。。。

分类讨论:

1.假如当前集合是一棵树(点数=边数+1),那么将ans乘上边数点数

2.假如当前集合是一个环加外向树(点数=边数),那么只有两种方案,ans*=2

3.假如 边数>点数,那么就没有解,ans=0

code:

int n,m,x,y,e[100010],d[100010],fa[100010],ans=1;

int getfa(int x){

if(fa[x]==x) return x;

return fa[x]=getfa(fa[x]);

}

int main(){

freopen("girl.in","r",stdin);

freopen("girl.out","w",stdout);

scanf("%d%d",&n,&m);

for(int i=1;i<=n;i++)fa[i]=i,d[i]=1; 

for(int i=1;i<=m;i++){

    scanf("%d%d",&x,&y);

    int fx=getfa(x),fy=getfa(y);

    if(fx==fy) e[fx]++; 

    else{

        fa[fx]=fy;

        d[fy]+=d[fx];

        e[fy]+=e[fx]+1;

    }

}

for(int i=1;i<=n;i++)

    if(fa[i]==i){

        if(e[i]>d[i]) ans=0;

        else if(e[i]==d[i]) ans=ans*2%mod;

        else ans=1ll*ans*d[i]%mod;

    }

printf("%d",ans%mod);

return 0;

}

T4:

这是一道找规律题,暴力能够40分(从0~n枚举,进制转换判断),但是我们是有梦想的人。。

经过找规律(其实是证明)发现,当i(k进制)=i(-k进制)时,只有偶数位上会有值(因为负数的偶次方等于正数)

然后通过数学方法求得方案数(其实就是数字个数)

code:

long long n,k,ans;

int a[110],m;

int main(){

freopen("endless.in","r",stdin);

freopen("endless.out","w",stdout);

scanf("%lld%lld",&n,&k);

while(n){

    a[++m]=(int)n%k;

    n/=k;

} 

for(int i=m;i>=1;i--){

    if(!a[i]) continue;

    if(!(i&1)){ans+=(long long)pow(k,(i+1)/2);break;}

    else ans+=(long long)pow(k,i/2)*a[i];

}

printf("%lld",ans);

return 0;

}

T5:

这本来说是线段树的水题,结果一看不是模板就不会做了啊,。

刚开始真的以为是什么可持久化线段树。。。

其实k<=10是可以用很暴力的方法做出来的。。

与区间求最值没有什么区别,只是还需要储存区间最值的位置,然后当我们求得区间最值时,将区间裂为两个,使得最大值不在其中

T6:

这是防AK题,所以很难。。

每次二分出一段区间,然后搜索一遍,判断是否能从1~n,知道不行,就能得出最长的区间(因为要求最少分组)

然后还需要求r的范围,但是由于我太弱,不打了。。

但是要注意,如果每次都要用memset来清空图的话,复杂度会达到O(n^2)。。

所以只需要每次将以l~mid为起点的边给删了就好了。。

code:

int l,r,mid,u,v,n,m,cnt,t,ans,vis[200010];

struct node{

int a,b;

}e[500010];

vector ed[200010];

void dfs(int x,int y){

if(vis[x]==t) return;

if(x==y){cnt++;return;}//编号相同,就到达了 

vis[x]=t;

for(int i=0;i<ed[x].size();i++)dfs(ed[x][i],y);//这应该是DFS吧。。 

//puts("1");

}

bool check(int x,int y){

for(int i=x;i<=y;i++)ed[e[i].a].push_back(e[i].b);//建图 

cnt=0;//cnt代表从1~n的方案数 

t++;//时间戳? 

dfs(1,n);//开始搜 

//puts("2");

for(int i=x;i<=y;i++)ed[e[i].a].clear();

return cnt>0;//方案数大于0说明可以到达

}

void read(int &x){

char ch=getchar();

x=0;

while(ch<'0'||ch>'9') ch=getchar();

while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();

}

int main(){

freopen("road.in","r",stdin);

freopen("road.out","w",stdout);

read(n);read(m);

for(int i=1;i<=m;i++){

    read(u);read(v);

    e[i]=(node){u,v};

}

for(int i=1;i<=m;i++){

    l=i;r=m;

    while(l<=r){

        mid=l+r>>1;

        if(check(i,mid)) r=mid-1;

        else l=mid+1;  

    }

    i=r;

    ans++;

    //printf("%d\n",ans);

}

printf("%d",ans);

return 0;

}

这只是60分的代码。。

过几天加上预处理r的范围,就可以A了吧。。

普及转提高第三场训练博客

2017-12-16 23:29:22 By heqingyu

听说这次题目很水,于是就稍微有点不垫底的信心了。。

第一题本来是满的啊。。结果打程序时手抖把“return 0”写成“return 9”了。。

第二题乱敲,过了样例,然而没有什么卵用。。

第三四五题不是暴力就是题意没看懂乱敲的。。。

第六题打了个暴力,没加优化。。

现在来梳理一下今天的题解:

T1:

十分水的模拟题,不需要考虑任何东西。。

code:

using namespace std;

int n,a[1000010];

long long cnt;

void read(int &x){

x=0;

char ch=getchar();

while(ch>'9'||ch<'0') ch=getchar();

while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();

}

int main(){

freopen("book.in","r",stdin);

freopen("book.out","w",stdout);

read(n);

for(int i=1;i<=n;i++){

    read(a[i]);

    //cout<<cnt<<endl; 

    if(a[i]==5) cnt+=5;

    if(a[i]==10){

        if(cnt<5){puts("NO");return 0;}

        else cnt+=5;

    }

    if(a[i]==20){

        if(cnt<15){puts("NO");return 0;}

        else cnt+=15;

    }

}

puts("YES"); 

return 0;

}

T2:

这道题题意写的云里雾里,我表示没看懂啊。。

但是经过老师讲解,总算是慢慢看懂了。。。

其实就是求组合计数,但是需要用上逆元和费马小定理。。

code:

define mod 1000000007

using namespace std;

int n,k;

long long ans=1;

long long solve(long long x,long long y){

long long now=x,cnt=1;

for(;y;y>>=1,now=now*now%mod)if(y&1) cnt=cnt*now%mod;

return cnt;

}

int main(){

freopen("cube.in","r",stdin);

freopen("cube.out","w",stdout);

scanf("%d%d",&n,&k);

for(int i=1;i<=n;i++)ans=1ll*ans*i%mod;

for(int i=1;i<=k;i++)ans=1ll*ans*solve(i,mod-2)%mod;

for(int i=1;i<=n-k;i++)ans=1ll*ans*solve(i,mod-2)%mod;    

printf("%lld",ans);

return 0;

}

T3:

这道题目本来都快要做出来了,我是用set的。。

但是结果由于set太烦,迭代器不好用,所以就没写下去。。

这道题主要是一个逆向思维的贪心:

1.将两个零合并,不合并非零数(因为这样就会减得慢)

2.其他数减一

就这样过了。。

code:

using namespace std;

int n,x,ans,ma,a[1000010],cnt;

int main(){

freopen("multiset.in","r",stdin);

freopen("multiset.out","w",stdout);

scanf("%d",&n);

for(int i=1;i<=n;i++){

    scanf("%d",&x);

    a[x]++;

    ma=max(ma,x);

}

cnt=a[0];

for(int i=1;i<=ma;i++)cnt=(cnt+1)/2+a[i];

ans=ma; 

while(cnt>1){cnt=(cnt+1)/2;ans++;}

printf("%d",ans);        

return 0;

}

T4:

这道题主要难点在于想到数位DP。。

再加上题意的确是不太明确啊。。

所以干脆就乱搞一下,结果就0分

最后听了课之后,勉强敲了下来,但是感觉没有完全消化掉。。过会再消化吧。。

code:

using namespace std;

int n,a[100010],b[100010];

long long f[100010][2];

char s[100010];

void read(int &x){

char ch=getchar();

int f=1; 

while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}

while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();

x*=f;

}

int main(){

freopen("maximum.in","r",stdin);

freopen("maximum.out","w",stdout);

scanf("%d",&n);

for(int i=1;i<=n;i++)read(a[i]);

scanf("%s",s+1);

assert(strlen(s+1)==n); 

for(int i=1;i<=strlen(s+1);i++)b[i]=s[i]-'0';

for(int i=1;i<=n;i++)

    for(int j=0;j<=1;j++)

        for(int k=0;k<=1;k++)

            f[i][k<b[i]||(k==b[i]&&j)]=max(f[i-1][j]+1LL*k*a[i],f[i][k<b[i]||(k==b[i]&&j)]);

printf("%lld",f[n][1]);

return 0;

}

T5:

这道题看到“最大值最小”的时候,就想到应该是用二分,但是这么强的DP着实没有想到啊。。

首先二分出答案,check一下mid,主要就是这个check函数特别难想到DP上去。。

状态转移方程:

if abs a[i]-a[j]<=mid*(i-j) f[i]=max(f[i],f[j]+1);

这里的mid*(i-j)是指区间每两个数的差值之绝对值不超过mid,那么乘以区间长度就行了

code:

using namespace std;

long long l,r,n,k,a[1010],f[1010],ans=1e9;

bool check(long long x){

long long cnt=0;

memset(f,0,sizeof(f));

for(int i=1;i<=n;i++){

    f[i]=1;

    for(int j=1;j<i;j++)    

        if((long long)abs(a[i]-a[j])<=(long long)x*(i-j)) f[i]=max(f[i],f[j]+1);        

}

for(int i=1;i<=n;i++)cnt=max(cnt,f[i]);

return n-cnt<=k;

}

int main(){

freopen("minimum.in","r",stdin);

freopen("minimum.out","w",stdout);

scanf("%lld%lld",&n,&k);

int num=0;                

for(int i=1;i<=n;i++)scanf("%lld",&a[i]);

r=1e9+1;l=-1;                    

while(l+1<r){            

    long long mid=l+r>>1;

    if(check(mid)) r=mid;

    else l=mid;                     

}                        

printf("%lld",r);        

return 0;

}

T6:

这题我只会30分做法,就是记录前缀和cnt[i],表示前i个字符中A的数量(或者B的数量);

到时候只需要枚举l~r这段区间,然后记录答案就行了。。复杂度O(q*n^2);

code:

using namespace std;

int n,x,y,q,l,r,cnt[100010],cnt1[100010];

char s[100010];

void read(int &x){

x=0;

char ch=getchar();

while(ch>'9'||ch<'0') ch=getchar(); 

while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();

}

int main(){

freopen("sequence.in","r",stdin);

freopen("sequence.out","w",stdout);

read(n);read(x);read(y);

x/=__gcd(x,y);y/=__gcd(x,y);

for(int i=1;i<=n;i++){

    s[i]=getchar();

    cnt[i]=cnt[i-1];

    cnt1[i]=cnt1[i-1];

    if(s[i]=='A') cnt[i]++;

    else cnt1[i]++;

    //cout<<s[i]<<endl;

}

scanf("%d",&q);

while(q--){

    scanf("%d%d",&l,&r);

    int ans=0;

    for(int i=l;i<=r;i++)

        for(int j=i;j<=r;j++){

            int xt=cnt[j]-cnt[i-1],yt=cnt1[j]-cnt1[i-1];

            int d=__gcd(xt,yt);

            xt/=__gcd(xt,yt);yt/=d;

            if(xt==x&&yt==y) ans=max(ans,j-i+1);

        }

    printf("%d\n",ans);

}

//for(int i=1;i<=n;i++)printf("%d %d\n",cnt[i],cnt1[i]); 


return 0;

}

洛谷P1113

2017-10-12 13:39:09 By heqingyu

这题有三个解法(其中一个不太会。。。):

算法一:贪心,因为已经有顺序了。。就是求一下做前n个任务需要的最小时间,然后就好了。。

include

using namespace std;

int n,k,f[10010],ans,t;

int main(){

scanf("%d",&n);

for(int i=1;i<=n;i++){

    scanf("%d%d",&k,&t);

    while(scanf("%d",&k)&&k) f[i]=max(f[i],f[k]);

    f[i]+=t;

    ans=max(ans,f[i]);

}

printf("%d",ans);

return 0;

}

算法二:最短路算法,换一个松弛方向,就可以了。。

因为这题是保证无环的,所以可以直接建图后跑一遍最长路就行了,

为了优化一下复杂度,先建一个点0,就可以让从第0个任务开始求了:

include

using namespace std;

int n,len[10010],q[10010],d[10010];

bool ro[10010],vis[10010];

vector g[10010];

int spfa(int s){

int l=0,r=1;

q[l]=s;

vis[s]=true;

memset(d,0,sizeof d);

while(l!=r){

    int u=q[l++];l%=10007;vis[u]=false;

    for(int i=0;i<g[u].size();i++){

        int v=g[u][i];

        if(d[v]<d[u]+len[v]){

            d[v]=d[u]+len[v];

            if(!vis[v]){

                q[r++]=v;

                r%=10007;

                vis[v]=true;

            }

        }

    }

}

int ans=0;

for(int i=1;i<=n;i++)if(d[i]>ans)ans=d[i];

return ans;

}

int main(){

scanf("%d",&n);

for(int i=1,id,x;i<=n;i++){

    scanf("%d%d",&id,len+i);

    while(1){

        scanf("%d",&x);

        if(x==0) break;

        g[x].push_back(id);

        ro[id]=true;

    }
}
for(int i=1;i<=n;i++) if(!ro[i]) g[0].push_back(i);

printf("%d", spfa(0));

return 0;

}

算法三:拓扑排序(标算):

我不会。。。

洛谷P3367

2017-10-12 12:51:05 By heqingyu

并查集模板。。。有些语句十分神奇,能够一句话完成一个操作。。

如果你不会并查集算法的话,可以去网上搜一下。。。

include

using namespace std;

int n,m,z,x,y,fa[100010];

int getfa(int x){return x==fa[x]?x:fa[x]=getfa(fa[x]);} //压缩版的getfather函数。。。加了优化(路径压缩)

int main(){

scanf("%d%d",&n,&m);

for(int i=1;i<=n;i++)fa[i]=i;// 初始所有点的father都是自己

for(int i=1;i<=m;i++){

    scanf("%d%d%d",&z,&x,&y);

    if(z==1){fa[getfa(x)]=getfa(y);}//压缩,神奇

    else{

    if(getfa(x)==getfa(y)) puts("Y");//判断祖先相不相同,如果相同,则是同一集合内的,输出Y

    else puts("N");

    }

}

return 0;

}

共 24 篇博客