PKUSC预热__被水题虐QoQ

感觉最近有些不在状态,几场考试考的都不是非常好.
然后我这种大弱渣显然只能去P啦~~~
(虽然觉得ACM赛制还是要跪
不过总要做出选择吗.

POJ1832

首先递推算出 fi 表示第 i 位到第0位全部变成 0 的最小移动次数.
gi表示将第 i 位变成1,同时第 i1 位到第 0 位都变成0的最小移动次数.
利用数学归纳法可以得到:
若有一个在第 i 位的1,将它变成 0 需要的最小步数为2i+11.
从而递推式就很容易写出来了.(我太懒就略过了
然后我们可以模拟变的过程,首先找到最高的不相同的位,然后对后面调用 g ,然后后面就变成了10000000...这样的情况,再从高到低依次模拟就行了.
显然需要高精度.
另外还有一种黑科技就是求出格雷码并转化为十进制数直接求差的绝对值即可.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;

static const int mod=1e4;
struct Hugeint{
    int d[1000],l;
    Hugeint():l(1){
        memset(d,0,sizeof d);
    }

    inline void operator*=(const int&x){
        int t=0;
        for(int i=0;i<l;++i){
            t+=d[i]*x;
            d[i]=t%mod;
            t/=mod;
        }
        if(t)
            d[l++]=t;
    }
    inline void operator+=(const Hugeint&B){
        l=l>B.l?l:B.l;
        for(int i=0;i<l;++i){
            d[i]+=B.d[i];
            if(d[i]>=mod){
                d[i]-=mod;
                d[i+1]++;
            }
        }
        if(d[l])
            ++l;
    }
    inline void output(){
        printf("%d",d[l-1]);
        for(int i=l-2;i>=0;--i)
            printf("%04d",d[i]);
    }
};

Hugeint pow[130];

int a[128],b[128];
Hugeint f[130],g[130];

int main(){
    //freopen("tt.in","r",stdin);
    int T,n,i,j;
    pow[0].d[0]=1;
    for(i=1;i<128;++i){
        pow[i]=pow[i-1];
        pow[i]*=2;
    }
    cin>>T;
    for(int Tcase=1;Tcase<=T;++Tcase){
        cin>>n;
        for(i=n-1;i>=0;--i)
            cin>>a[i];
        for(i=n-1;i>=0;--i)
            cin>>b[i];

        f[0].d[0]=g[0].d[0]=0;
        if(a[0]==0)
            g[0].d[0]=1;
        else
            f[0].d[0]=1;
        for(i=1;i<n;++i){
            if(a[i]==0){
                f[i]=f[i-1];
                g[i]=g[i-1],g[i]+=pow[i];
            }
            else{
                f[i]=g[i-1],f[i]+=pow[i];
                g[i]=f[i-1];
            }
        }
        Hugeint res;
        int ins=-1;
        for(i=n-1;i>=0;--i)
            if(a[i]!=b[i]){
                ins=i;
                break;
            }
        if(ins<0)
            puts("0");
        else{
            if(ins)
                res+=g[ins-1];
            res+=pow[0];
            if(ins>0){
                memset(a,0,sizeof a);
                a[ins-1]=1;
                for(i=ins-1;i>=0;--i){
                    if(a[i]!=b[i]){
                        res+=pow[i];
                        if(i)
                            a[i-1]=1;
                    }
                }
            }
            res.output();
            puts("");
        }
    }
    return 0;
}

POJ1112

首先建立反图,若两个点之间有连边则证明不能在一个集合中.
这让我们联想到二分图.
于是对于每一个联通分量进行二染色,若存在一个连通分量不能二染色则无解.
注意题目让我们找出一组最接近的解.
对于每个联通分量,我们将染色的方案存下来,那么有用的仅仅是两个部分分别属于哪个集合.
我们做一次简单的dp并记录方案即可.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

#define N 110
int head[N],next[N*N<<1],end[N*N<<1];
inline void addedge(int a,int b){
    static int q=1;
    end[q]=b;
    next[q]=head[a];
    head[a]=q++;
}
inline void make(int a,int b){
    addedge(a,b);
    addedge(b,a);
}

bool G[N][N];

int v[N];

int cnt;
vector<int>s[N][2];
bool f[N][N],g[N][N];

inline bool dfs(int x){
    if(v[x]==-1){
        v[x]=1;
        s[cnt][1].push_back(x);
    }
    for(int j=head[x];j;j=next[j]){
        if(v[end[j]]==-1){
            v[end[j]]=1-v[x];
            s[cnt][1-v[x]].push_back(end[j]);
            if(!dfs(end[j]))
                return 0;
        }
        else if(v[end[j]]==v[x])
            return 0;
    }
    return 1;
}
inline int _abs(int x){
    return x<0?-x:x;
}

bool ok[N];

inline void find(int x,int y){
    if(x==0)
        return;
    if(g[x][y]==0){
        for(int i=0;i<s[x][0].size();++i)
            ok[s[x][0][i]]=1;
        find(x-1,y-s[x][0].size());
    }
    else{
        for(int i=0;i<s[x][1].size();++i)
            ok[s[x][1][i]]=1;
        find(x-1,y-s[x][1].size());
    }
}

int main(){
    int n;
    scanf("%d",&n);
    int i,j,x;
    for(i=1;i<=n;++i){
        while(scanf("%d",&x)&&x)
            G[i][x]=1;
    }
    for(i=1;i<=n;++i)
        for(j=i+1;j<=n;++j)
            if(!(G[i][j]&&G[j][i]))
                make(i,j);
    memset(v,-1,sizeof v);
    bool nosol=0;
    for(i=1;i<=n;++i){
        if(v[i]==-1){
            ++cnt;
            if(!dfs(i)){
                nosol=1;
                break;
            }
        }
    }
    if(nosol)
        puts("No solution");
    else{
        f[1][s[1][1].size()]=1;
        g[1][s[1][1].size()]=1;
        f[1][s[1][0].size()]=1;
        g[1][s[1][0].size()]=0;
        for(i=1;i<cnt;++i)
            for(j=0;j<=n;++j)
                if(f[i][j]){
                    f[i+1][j+s[i+1][0].size()]=1;
                    g[i+1][j+s[i+1][0].size()]=0;
                    f[i+1][j+s[i+1][1].size()]=1;
                    g[i+1][j+s[i+1][1].size()]=1;
                }
        int ans=0x3f3f3f3f,num;
        for(i=0;i<=n;++i){
            if(f[cnt][i]&&_abs(i-(n-i))<ans){
                ans=_abs(i-(n-i));
                num=i;
            }
        }
        find(cnt,num);
        vector<int>v1,v2;
        for(i=1;i<=n;++i)
            if(ok[i])
                v1.push_back(i);
            else
                v2.push_back(i);
        printf("%d",v1.size());
        for(i=0;i<v1.size();++i)
            printf(" %d",v1[i]);
        puts("");
        printf("%d",v2.size());
        for(i=0;i<v2.size();++i)
            printf(" %d",v2[i]);
    }
    return 0;
}

POJ2238

题目水的一比,只需要暴力枚举自己的四项属性,然后做一次dp看一看此时的获胜概率是多少就行了.
fi,j,0,fi,j,1 分别表示自己得到 i 分,另一个人得到j分,下一次自己/别人出手的概率.
fi,j,0,fi,j,1 缩成一个强连通分量,不难发现状态转移图是一个拓扑图.
于是只需要套用一般的方法:对于每一个连通分量先处理这个连通分量里面的答案,然后将概率转移到后续的连通分量里面的点,然后再后面的连通分量里面再处理就行了.
对于一个连通分量,里面的转移是存在环的,因此要列方程进行求解.
对于分数二元组 (i,j) ,令 p0 表示 fi,j,0 ,令 p1 表示 fi,j,1 ,则有以下的方程:

p0=stay0p0+change1p1+c0

p1=stay1p1+change0p0+c1

其中 c0,c1 表示从上面的连通分量转移下来的贡献.
随便解个方程就行了.
现在问题的关键是源点怎么转移.
(跪大爷
我们可以在两个方程中的一个加上 p0=1 ,这样我们惊奇的发现得到正确的答案啦!
但是这样转移下去是并不对的!
上述的正确答案是指只有这个连通分量时的正确答案.
也就是说源点本可以以更大的概率转移到下面的分量.
因此我们需要将这个连通分量里面所有的点的概率都除以原点的概率!(雾
其实我得出的结论是:只要方程是对的,随便搞搞就能得出正确的结果啦!
然后也很容易对拍:不断迭代就行啦!

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

typedef double db;
int n,m;
int pt2[2],pt3[2],reb[2],def[2];

db ans=0;

db f[35][35][2],g[35][35][2];
db ok3[2],ok2[2],stay[2],change[2];

typedef pair<int,int> pii;
int deg[35][35];
bool vis[35][35];

static const int dx[4]={2,0,3,0};
static const int dy[4]={0,2,0,3};

pair<db,db>solve(db a1,db b1,db c1,db a2,db b2,db c2){
    db x,y;
    y=(c1*a2-c2*a1)/(b1*a2-a1*b2);
    x=(c1-b1*y)/a1;
    return make_pair(x,y);
}

inline db dp(){
    int i,j,k;
    for(i=0;i<2;++i){
        db p=pt3[i]/(db)(pt2[i]+pt3[i]);
        ok3[i]=p*0.8*pt3[i]/(db)(pt3[i]+def[1-i]);
        ok2[i]=(1-p)*pt2[i]/(db)(pt2[i]+def[1-i]);
        stay[i]=(1-ok3[i]-ok2[i])*0.8*reb[i]/(db)(reb[0]+reb[1]);
        change[i]=1-ok3[i]-ok2[i]-stay[i];
    }

    queue<pii>q;
    q.push(make_pair(0,0));
    memset(vis,0,sizeof vis);
    vis[0][0]=1;
    memset(deg,0,sizeof deg);
    pii tmp;
    while(!q.empty()){
        tmp=q.front();
        q.pop();
        int x=tmp.first,y=tmp.second;
        if(x>=n||y>=n)
            continue;
        for(i=0;i<4;++i){
            ++deg[x+dx[i]][y+dy[i]];
            if(!vis[x+dx[i]][y+dy[i]]){
                vis[x+dx[i]][y+dy[i]]=1;
                q.push(make_pair(x+dx[i],y+dy[i]));
            }
        }
    }

    memset(f,0,sizeof f);
    memset(g,0,sizeof g);
    db re=0;
    q.push(make_pair(0,0));
    while(!q.empty()){
        tmp=q.front();
        q.pop();
        int x=tmp.first,y=tmp.second;
        pair<db,db>get;
        if(x==0&&y==0){
            get=solve(2-stay[0],-change[1],1,-change[0],1-stay[1],0);
            get.second/=get.first;
            get.first=1;
        }
        else
            get=solve(1-stay[0],-change[1],g[x][y][0],-change[0],1-stay[1],g[x][y][1]);
        f[x][y][0]=get.first;
        f[x][y][1]=get.second;

        if(x>=n||y>=n)
            continue;
        g[x+2][y][0]+=f[x][y][0]*ok2[0];
        g[x][y+2][1]+=f[x][y][1]*ok2[1];
        g[x+3][y][0]+=f[x][y][0]*ok3[0];
        g[x][y+3][1]+=f[x][y][1]*ok3[1];

        for(i=0;i<4;++i)
            if(!(--deg[x+dx[i]][y+dy[i]]))
                q.push(make_pair(x+dx[i],y+dy[i]));
    }

    for(j=0;j<=34;++j)
        for(k=0;k<=34;++k)
            if((j>=n||k>=n)&&(j>k))
                re+=f[j][k][0];
    return re;

}


inline void dfs(int dep,int last){
    if(dep==5&&last==0){
        ans=max(ans,dp());
        return;
    }
    for(int i=1;i<=10&&i<=last;++i){
        if(dep==1)
            pt2[0]=i;
        else if(dep==2)
            pt3[0]=i;
        else if(dep==3)
            reb[0]=i;
        else
            def[0]=i;
        if(dep==3){
            if(last-i>=1&&last-i<=10)
                dfs(dep+1,last-i);
        }
        else
            dfs(dep+1,last-i);
    }
}

int main(){
    while(scanf("%d%d%d%d%d%d",&n,&m,&pt2[1],&pt3[1],&reb[1],&def[1])!=EOF){
        ans=0;
        dfs(1,m);
        printf("%.3lf\n",ans);
    }
    return 0;
}

POJ2054

首先注意到限制等价于每个点的父亲都比这个点要早选择.
若没有任何限制,显然应该按照权值从大到小选择.
现在有限制,我们找到权值最大的那个点,那么我们期望它尽可能早的被选择.
那么他和他的父亲在选择序列中必定是连续的,因为如果不连续,我们可以将这个点的选择时间不断前移,依旧满足所有限制条件并且答案不会增大.
那么我们可以把这两个点缩成一个点!这个点权值为原来所有点的平均值.
为什么是平均值呢?
不妨举一个简单的例子:
考虑两个连通分量 s1,s2 ,按照已经确定的顺序,里面权值的顺序分别是:
s1:x1,x2
s2:x3,x4,x5
考虑若 s1,s2 优于 s2,s1 ,这证明:

x1+2x2+3x3+4x4+5x5<x3+2x4+3x5+4x1+5x2

于是得到:
x1+x22>x3+x4+x53

由于我们是要把权值较大的排在前面,因此我们能够发现这样重新计算权值是合理的.
于是随便乱搞就行了.(窝是口胡选手

POJ3420

状压dp+矩阵乘法

POJ4047

题目的意思是维护一个序列,支持单点修改,支持交换两个位置的元素,并询问一段区间内总和最大的长度为 k 的子段的和是多少.
一开始看成了在区间内找恰好k个子段使得总和最大.
这个可以模拟费用流利用线段树维护,可以在 O(klogn) 的时间内出解.
但是这样是不超过 k 个子段,而不是恰好k个子段.
至于怎么处理“恰好”,我想到了一个比较捉急的方法,我们可以在增广 k 次的前提下,再增广一次,如果答案不变,那么贪心选择区间前k大的元素.
回到原题,线段树傻逼题,就不说了.

POJ1647

细节题+大模拟.
(感觉不是很麻烦?随便写写就行了.

POJ1818

首先答案显然满足二分性质,不妨令 x 为当前判定的元素.
考虑最后一轮,在最优意义下,x必定与 xk 进行比赛.
这是因为 xk 是能被 x 打败且最容易留到最后一轮的元素.
再考虑倒数第二轮?
同样,给这两个元素分配他们能打败的最强的对手.
于是这样一轮一轮的分配,若所有人都被分配到了,证明存在一组可行解.

POJ3986

题意:
x1 xor x2... xor xn=m的非负整数解的个数,其中对于 1in 需要满足 xiyi .
数据范围 n50,yi,m109.
感觉对于这种思路还不是很熟悉吧…有点脑残.
首先对于所有数排序,找到最大的那个数的最高非零位.
仅仅考虑这一位,如果最终结果的这一位为 0 ,剩下的数(除了最大的数的那些数)异或起来这一位也为0,那么限制最大的那个数的选择方案是唯一的,并且此时的方案数就等于剩下的那些数异或和这一位为 0 的方案数.
对于这个,我们可以用一个简单的dp来计算.注意要满足限制.
现在问题来了:如果要求异或和这一位为0,而剩下的数异或起来这一位却为 1 怎么办?
那么限制最大的那个数的选择方案依然是唯一的———但却不一定能够取到!
不过我们已经确定了这个数这一位必定要选1,并且这跟刚才的方案必定是互不相交的!
那么我们只需要确定这个数后面的那些位怎么选就行了.
于是我们可以将这个问题转化为一个和刚才相似的子问题:
若最大的数的限制是 x ,且最高非零位是第t位,我们就将这个数的限制变为 x2t ,其余数的限制不变.不过由于剩下数的异或和这一位需要为 1 ,因此他们异或的总和这一位需要为1(注意原问题异或和这一位需要为 0 ).
于是这样就能递归做下去了.
一些边界问题见代码.
(SRM564div1 850pts)

#include <vector>
#include <list>
#include <map>
#include <set>
#include <queue>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <cstring>
using namespace std;

typedef long long ll;
static const int mod=(1e9)+7;
inline void inc(int&x,int y){
    if((x+=y)>=mod)
        x-=mod;
}
int f[51][2];
class DefectiveAddition {
public:
    int count(vector <int> cards, int m) {
        int n=cards.size();
        sort(cards.begin(),cards.end());
        int t;
        for(int i=31;i>=0;--i)
            if((cards[n-1]>>i)&1){
                t=1<<i;
                break;
            }
        if(cards[n-1]==0)
            return m==0;
        if(m>=(t<<1))
            return 0;
        memset(f,0,sizeof f);
        f[0][0]=1;
        for(int i=0;i<n-1;++i)
            for(int j=0;j<2;++j){
                inc(f[i+1][j],(ll)f[i][j]*min(cards[i]+1,t)%mod);
                inc(f[i+1][j],(ll)f[i][j^1]*max(0,cards[i]-t+1)%mod);
            }
        int ans=f[n-1][((m&t)>0)?1:0];
        cards[n-1]-=t;
        inc(ans,count(cards,m^t));
        return ans;
    }
};

POJ2906

ST表优化+网络流

POJ3585

题意:给定一棵树,树上的每一条边都有流量限制,每个点最大流量值等于这个点到所有剩下的叶子节点的最大流.求所有点的最大流量值的最大值.
水水的树形dp啊.

POJ1777

数学题.
重要定理:一个数x的约数之和为 2 的若干次幂等价于x可以分解为若干个不相同的梅森素数的乘积.
由于在数据范围之内梅森素数非常少,随后只要看一个数拥有哪些梅森素数,然后状压dp就行了.

[WF2013]Self-Assembly

将一个正方形看成一个点,若两个正方形之间可以有一种方式连接则从 + 的一个正方形向的那个正方形连一条边.
可以证明,若最终的图存在一个环,则可以无限构造下去.
但是这样边数和点数都太多了.
我们注意到有用的只有52个点.
在一个正方形内部,比如说有 A+,B ,那我们连接 A+B+,BA ,这样的话如果这个图有环的话也可以证明可以无限构造.
这样就可以了.

[WF2013]Surely You Congest

首先需要注意到到点 1 距离不同的点之间不可能互相干扰.
这是因为他们同时出发,每次只走最短路,不可能同时走到和点1最短路相同的位置.
那么仅需要考虑的是到点 1 距离相同的所有点.
建出最短路图,设流量限制为1,然后随便求一个最大流就行了.

[WF2013]Factors

发现可行的状态数很少,于是预处理一个表然后在表里面找.

[WF2013]Low Power

首先排序,二分答案,然后尽可能选择靠前的满足条件的连续对.然后判定一下在此种选择之下能否将那些较大的分配出去.
判定条件自己想想(窝TM连这东西都不会啦!QwQ

BZOJ3777-3779已经风化在历史中…

(手贱没存盘,题解就这样没了QwQ

POJ1708

f[i][j] 表示一个到达 i ,另外一个到达j的最小步数, O(n3) 傻逼题.

[WF2013]Матрёшка

区间dp,令 f[i][j] 表示将区间 [i,j] 里面的套娃合并成一个大套娃的最小代价.
考虑转移 f[i][k] 以及 f[k+1][j] ,若两端区间里面的最大值相同,显然不能合成一个套娃.值为 INF .
否则进行分类讨论:
另两个区间最小值分别为 x1,x2 ,不妨令 x1x2 .
x1<x2 ,则代价等于总区间长度减去 x1 所在区间中值在 [x1,x21] 的个数.
x1=x2 ,则代价等于总区间长度减去两个区间中值为 x1 的个数.
这东西怎么算?
首先 O(n3) 预处理出 g[i][j][k] 表示前 k 个数中值在[i,j]的个数.
于是回答上面的问题只需要 O(1) 利用前缀和相减就行了.
求区间最值暴力预处理出来就行了.
于是时间复杂度为 O(n3) .

[WF2013]Pirate Chest

首先令箱子底部为 wl ,高度为 h ,同时放置区域的深度最小值为d,那么应该满足:

d+wlhnm>h

整理一下得到:
(1wlnm)h<d

h<nmdnmwl

所以有 h=nmd1nmwl .
考虑 w,d 确定时, l 越大h则越大,同时我们需要最大化 wlh ,所以应最大化 l .
考虑枚举w同时再枚举箱子的第一行是哪一行,这样相当于转化为了一个序列问题,现在假定 d 已经确定,我们要最大化l,那么肯定是尽可能向两边延伸.于是我们利用单调栈 O(n) 解决这个问题即可.
于是时间复杂度为 O(n3) .

[WF2013]Harvard

(感觉这些所有的东西都是在扒贾教题解…
直接枚举集合划分.
然后对于两个连续的访问,若两个元素所在的区域不同且后一个元素不在 0 号区域那么就会带来1的额外设置代价.
可以预处理出 f[i][j] 表示 i,j 两个变量连续访问的次数.
集合划分的枚举量不超过 B13 ,又由于变量最多有 13 个,于是复杂度就是 O(132B13) .
又由于时间限制为 10s ,大概这样就能过了.

POJ3594

拆点最短路即视感(感觉能被卡掉?

POJ3595

推了一下午公式,然后发现是错的而且根本不知道哪里不对.
学到一个近似公式 n!=2πn(ne)neλn ,其中 112n+1<λn<112n .
然而我依然不知道我的公式哪里不对TAT.
知道这个姿势就行了吧哈哈哈哈.
upd:其实这道题目还是不错的.
首先要找出递推公式: fn=(2n5)fn1 ,递归边界是 f3=1 .
为什么是这个呢?
考虑现在我们要加入一个叶子以及一个中间节点,首先他们两个一定要连在一下,然后中间节点还有两个度谁来提供呢?
原来的树都是满度数的,我们考虑任意断去一条边,然后用这个中间节点来代替这两条边,这样就可以了.
而原来的树的边数为 2(n2)21=2n5 ,这个式子就是这样来的.
然后能够得到: fn=(2n5)!2n3(n3)! .
所以有 log(fn)=log((2n5)!)log((n3)!)(n3)log2 .
那么 log(n!) 等于什么呢?
根据上面的公式, log(n!)=0.5(log(2π)+logn)+n(logn1)+λn .
然后我们就有 logefn 辣!
然后随便推一下式子变成 log10fn .
然后这个东西的整数部分就是 E 后面的数!这个东西的小数部分就是前面的数!
使用一个神奇的函数double modf(double,double),第一个参数表示整个小数,第二个参数表示整数部分,然后返回整个小数的小数部分.
然后再随便搞搞就行了.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

typedef long long ll;
typedef double f2;
inline f2 log_fac(int n){
    return 1.0/(12*n)+n*(log((f2)n)-1)+0.5*(log(2*acos(-1.0))+log((f2)n));
}
inline void work(int n){
    if(n<=10){
        ll ans=1;
        for(int i=3;i<=n;++i)
            ans*=(2*i-5);
        int pos=0;
        ll tmp=ans;
        while(1){
            if(tmp<10)
                break;
            ++pos;
            tmp/=10;
        }
        ll _tmp=ans*1000;
        for(int i=1;i<=pos;++i)
            _tmp/=10;
        printf("%d.%03dE%d\n",(int)_tmp/1000,(int)_tmp%1000,pos);
    }
    else{
        f2 log_e=log_fac(2*n-5)-log_fac(n-3)-(n-3)*log(2.0);
        f2 log_10=log_e/log(10.0);
        f2 num;
        f2 last=modf(log_10,&num);
        printf("%.3lfE%d\n",exp(last*log(10.0)),(int)num);
    }
}
int main(){
    int n;
    while(scanf("%d",&n)!=EOF)
        work(n);
    return 0;
}

*POJ3596

三维计算几何我并不会.
(感觉POJ月赛真是千古大坑…三无产品毛都没有啊QoQ

[NEERC14]A

简单的贪心一下,感觉没有什么不对的.

BZOJ2280: [Poi2011]Plot

首先二分答案,然后如果直接按照随机增量法做最小圆覆盖,依次尝试添加点,肯定会被卡成最坏 O(n3) 的情况,就完蛋了.
然后如果二分呢?
令划分的最小段数为 m ,这样的复杂度最坏是O(mnlogn)的,也并不行.
于是我们可以对于当前位置,枚举找出一个整数 k ,使得以当前位置为起点,长度为2k的区间是合法的,长度为 2k+1 的区间是不合法的.
然后就可以在其中二分了.
这样可以保证时间复杂度是 O(nlogn) 的!
于是总时间复杂度就是 O(nlog2n) 的了.

(插播广告之Lucas定理的证明

不妨令 n=ki=0nipi,m=ki=0mipi,0ni,mi<p,0mn .
然后现在我们要证明的是:

Cmni=0kCmini(modp)

首先有一个科学的等式,若 p 为质数则有:
(1+x)p1+xp(modp)

这是因为左侧对于任意 1i<p , xi 的系数为 Cip ,然后这东西等于 p!i!(pi)! ,肉眼观察一下上面的 p 是不可能除掉的,于是这东西就能被p整除.
所以这个等式是成立的.
然后考虑下面的式子:
(1+x)n=i=0k((1+x)pi)ni=i=0k(1+xpi)ni

然后考虑两边 xm 的系数.
左边肯定是 Cmn .
考虑右侧每一项的二项式展开,就是 nij=0Cjnixjpi .
注意我们最终是要得到 xm 的系数,考虑将 m 进行p进制分解,不难发现这一项只能选择 xmipi 这一项乘下去.
把它们的系数都找出来,我们就得到了我们要证明的式子:
Cmni=0kCmini(modp)

[UOJ#86]mx的组合数

(大家好我是BB选手
我们上面刚证完Lucas定理,现在就要用了.
首先考虑差分,这样限制就只有 r 了.
由于 p 是素数,在p进制下考虑数位dp.
首先将 n 进行p进制分解为 n=ki=0nipi ,然后预处理出 C[0,n0]n0,C[0,n1]n1,...,C[0,nk]nk .
由于 k 只有O(logp)级别,这个过程可以在 O(plogp) 的时间内完成.
接下来进行数位dp,令 f[i][j] 表示 p 进制后i位所有数对 p 取模余数为j的数的个数对于 998244353 取模的余数.
直接暴力转移是 O(p2logp) 的.
我们考虑求出 p 的原根g,并将状态改为 f[i][j] 表示 p 进制后i位所有数对 p 取模余数为gj的数的个数对于 998244353 取模的余数.
这样 f[i] 就能在 O(plogp) 的时间内由 f[i1] 进行一次卷积得到.
这样 O(plog2p) 就能完成预处理.
然后利用数位dp的思想按位确定就行了.
合并的时候也利用卷积来搞.
这样的总时间复杂度就是 O(plog2p) .
然后有一个小问题就是余数为 0 是没有原根的.这东西简单记录一下就好了吧.

[2015.5.23]A

题目大题:有一个二叉树,除了叶子节点的节点都有两个子结点.节点i有权值 ci .现在要选择一些叶子节点,对于叶子节点,若选择代价为 ci ,否则代价为 0 ;对于非叶子节点,代价为ci乘以左子树选择叶子节点数异或右子树选择叶子节点数.设总叶子节点数为 n ,现在对于1in,你要给出选择 i 个叶子节点时的最小总代价.
题解:不妨令f[i][j]表示以 i 为根的子树,选择j个叶子节点的最小代价.转移显然.然后看起来这样是 O(n3) 的,其实不然,这样的时间复杂度仅为 O(n2) .
F(i) 表示 i 的子树内部的dp全部做完花费的总时间代价,siz(i)表示以 i 为根的子树内的叶子节点数.
我们可以证明:F(i)siz(i)2.
对于叶子节点 i ,F(i)=siz(i)=1,显然成立.
对于非叶子节点 i ,有:

F(i)=siz(l(i))siz(r(i))+F(l(i))+F(r(i))

siz(l(i))2+siz(r(i))2+2siz(l(i))siz(r(i))

=(siz(l(i))+siz(r(i)))2=siz(i)2

对于根节点 root 使用此式子,由于 siz(root)<n ,因此就有 F(root)<n2 .
于是就可以了.

[2015.5.23]B

题目大意:有 n1 种两两不同的 A 舰以及n2种两两不同的 B 舰,现在要从两种战舰中各选出非负整数只战舰组成n3只战舰的一支战队.战队的战舰是有顺序的,但要求战队中的 A,B 战舰必须都按照原顺序出现.同时要求选择 A 战舰的数目对p取模的余数必须是给定的.求方案数对于 mod 取模的余数.
数据范围: 1sec,T200,1n1,n2,n31018,m,p103 .
题解:若令 A 舰选择m1只, B 舰选择m2只,则此时的方案数为 Cm1n1Cm2n2Cm1n3 .
让我们把以上的这些数都在 mod 进制下考虑.
我们知道 m1+m2=n3 ,假如他们相加的过程中在 mod 进制下进位了,那么意味着存在某一位的 m1i+m2i=mod+n3i ,于是有 m1i=modm2i+n3i>n3i .
而根据lucas定理, Cm1n3 存在 Cm1in3i 这个因数,于是在这种情况下的方案数对 mod 取模余数为 0 ,可以不用考虑!
如果没有进位那就好开心了是不是?直接对于每一位考虑即可.
对于每一位枚举mod进制下 m1 这一位选多少,然后直接将组合数算出来,然后将贡献累加到这个数对 p 取模的余数中去.
最后将每一位的数组卷积起来就行了.
(懒不想算复杂度了

[2015.5.23]C

题目大意:给定一棵边带权的树,对于每个点求出经过这个点的所有简单路径的异或和的最大值.
数据范围:n105.
思路:考虑点分治,在分治结构中处理出根的所有子树的前缀Trie以及后缀Trie,这样就能支持询问每个点为端点经过根的最大异或和路径.然后用这个值来更新这个分治结构中这个点的所有祖先.不过这样为什么就能正确更新答案了呢?(由于我懒)大家就好好思考一下吧: )
时间复杂度 O(32nlogn) .

POJ3597

题目大意:求将一个凸 n 边形划分为若干个三角形和四边形的方案数.
题解:我直到现在才明白将一个凸n边形划分为三角形为什么是卡特兰数…考虑固定凸多边形上的一条边,那么在任意个三角剖分中,这条边一定对应某个顶点,于是这个三角形将这个凸多边形划分成了三个部分,然后两边递归下去,方案相乘,这样发现就是卡特兰数的式子.
结果我纠结了好久方案重复的问题…
然后这道题也是一样的,就是固定一条边,然后考虑这条边对着的是什么.
如果是三角形,那我们直接用刚才的方法.
如果是四边形,那我们需要枚举一对二元组,我们可以枚举 i 表示二元组中的第一个点到固定的边的第一个点的距离,再枚举j表示二元组中的两个点的距离,然后就拆分成了三个部分,乘在一起就行了.
不妨令 fn 表示将凸 n 边形进行上述划分的方案数,然后有以下递推式:

fn=i=1n2fi+1fni+i=1n3j=1ni2fi+1fj+1fnij

然后发现是 O(n3) 的滚粗了.
我们再令 gn=n3j=1fj+1fnj1 ,然后原式就可以变成:

fn=i=1n1fi+1fni+i=1n3fi+1gni+1

在计算 f 的同时顺便计算g,就可以做到 O(n2) 了.
边界什么的直接手玩.

POJ3598

妈呀这题我都不会真是日狗了.
首先将所有点按照 x 为第一关键字,y为第二关键字排序,那么最上一层的最后一个点 (xk,yk) 必然是有序序列的最后一个点,那倒数第二个点必定是满足 x<xk,y>yk 的最后一个点,这样不断找直到找不到,我们就得到了最上层所有的点.
将这个过程利用线段树加速就行了.
(我是傻逼

BZOJ2613

题意:给定一个置换 b 以及正整数n,求置换 a ,使得an=b.
想法:考虑将 a 进行分解,分解成若干个循环,对于每个长度为L的循环,若进行 n 次自乘,则会变成ngcd(L,n)个长度为 gcd(L,n) 的子循环.
我们对于 b 进行循环分解,对于每种长度相同且为L的循环,考虑分解之前的长度若为 L ,则会满足L=Lgcd(L,n).那么在所有可行的 L 的集合中,必定存在最小的一个L整除余下所有可行的 L .
这是为什么呢?
考虑L的质因数分解 L=mi=1pqii ,令 L pi的幂次为 ri , n pi的幂次为 si ,显然对于 qi>0 pi ,有 ri=si+qi ,对于 qi=0 pi ,有 0ri=si ,那么(脑补一下)上面的结论就显然成立了.
由于题目保证有解,我们只需要找出最小的 L ,并将所有长度为L的循环都尝试合并成长度为 L 的循环即可.
怎么找出最小的L呢?根据上面的方法搞搞就行了.

【弱省胡策】Round #0 A

20%数据, O(n4) 傻逼dp.
40%数据, O(n3) 傻逼dp.
100%数据,令 f(x1,y1,x2,y2) 表示从 (x1,y1) 走到 (x2,y2) 的路径条数.于是所有路径就是 f(1,2,n1,m)×f(2,1,n,m1) .然而两条路径可能在中间的某个点相交,我们找出最早的交点,并在这个交点互换两条路径的后半部分,这样就成了一条 (1,2) (n,m1) 的路径以及一条 (2,1) (n1,m) 的路径,而且两者之间是一一对应关系.
于是答案等于:

f(1,2,n1,m)×f(2,1,n,m1)f(1,2,n,m1)×f(2,1,n1,m)

简单 O(n2) dp处理出四个量即可.

【弱省胡策】Round #0 B

首先要知道什么是DFA,就是一个自动机,在相同的转移下到达的节点一定相同.
分别建立两个串的后缀自动机以及子序列自动机,令 fi,j 表示在自动机 1 中转移到了i节点,同时在自动机 2 中转移到了j节点的串的数目.
由于是DFA,这里的转移显然不可能出现重复的串.
于是就是 O(|sigma|n2) 傻逼dp.
我图(作)方(大)便(死)写了傻逼 hash 结果变成傻逼.
给人感觉好像不能严格 n2 ?

JZPLCM

题目大意:给定一个正整数序列,每次询问一段区间内的所有数的最小公倍数对 109+7 取模的余数.
数据范围: n105 ,保证所有数都能够分解成不超过 100 的质数的乘积.
解法:对所有数质因数分解,若有一个 pq 的因子,则相当于这个位置有 p,p2,p3,...,pq q 个数出现,同时他们出现的代价都是p,这样问题就转化为了给定一段区间,求区间所有至少出现一次的数的代价之积.
而这是一个经典问题.
数的总数仅有 O(nlogn) 个,我们直接采用主席树维护即可.
若需要支持修改则可以用树状数组套权值线段树来维护.

CF Round305 div1A

玛德考场上写这题真是炖了狗了.
首先显然是找循环,然后我转化成了求二元一次方程 a1x+b1=a2y+b2 使得 x,y0 且最小的整数解.
于是就坑了接近一个小时,最终没有通过pretest.
事实证明这种方法(如果是我来写的话)必然要经过大量的分类讨论.
我们来看一种简单的方法吧.
首先对于 h1 暴力模拟,找出最早变成 a1 的时间 q ,若h2经过时间 q 也变成a2,则答案就是 q ;否则找出从a1出发又回到 a1 的最小时间 c ,让h2在经过时间 q 的基础上每次走时间c,直到能够变成 a2 .那么最多要走多少次呢?显然最多 m 次.不妨令d表示还需要走的步数,则显然答案就是 q+c×d .
于是我们就在 O(m) 的时间里解决了这道傻逼题.

CF Round305 div1B

首先注意到,对于 i ,ansi就是一个最大的 x ,使得存在一个长度为i的连续段最小值不小于 x .
于是我们按照权值从大到小插入所有数,并维护当前的最长连续段即可.
这一步可以简单地拿一个并查集来维护.
时间复杂度O(nα(n)).

CF Round305 div1C

f(x) 表示当前存在的所有数是 x 倍数的个数,于是互质的对数等于:

i=1MaxNμ(i)f(i)(f(i)1)2

考虑插入或者删除一个数,仅仅需要对于上式中他的约数进行修改.
而数字最大只有 5×105 ,约数个数是很少的,我们直接暴力修改就可以通过.
不过注意找出约数的复杂度必须是严格与约数个数相关的.

*CF Round305 div1D

(坑)

CF Round305 div1E

裸题+模板题.
我的思路:首先建立广义后缀树,然后问题就是问子树内的信息问题.
这样就是 O(nlogn) 的.
但是也可以建立后缀数组,然后通过预处理RMQ,上下二分得到子串所在的区间,于是这样一个位置只对应一个标号,就可以轻易拿主席树处理出来了.
这样也是 O(nlogn) 的.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值