洛谷 题目选做

~~bzoj早就老了~~现在最受欢迎的OJ当然是你谷了,一些比较经典的题目还是很不错的。

LINK:想了好几个月的dp 翻硬币 我想到一个状态 f[i][j]表示翻了i次且当前局面为j的方案数最后输出f[n][目标方案即可]。

但是这里面存在着一些问题 好像这个转移都是一个组合数级别的所以不可能转移出来的 。突然有一个想法设f[i][j]表示经过i次翻转有j个位置是合法的方案数这样就可以转移了。

n^2递推组合数即可。

//#include<bits/stdc++.h>
#include<iostream>
#include<queue>
#include<iomanip>
#include<cctype>
#include<cstdio>
#include<deque>
#include<utility>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 1000000000
#define ll long long
#define db double
#define mod 1000000007
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const ll MAXN=110;
ll n,m,k,cnt;
ll c[MAXN][MAXN];
ll f[MAXN][MAXN];//f[i][j]表示前i次翻转有j个位置合法的方案数。
char a[MAXN],b[MAXN];
inline void get_combination()
{
    for(ll i=0;i<=n;++i)c[i][0]=1;
    for(ll i=1;i<=n;++i)c[i][1]=i;
    for(ll i=1;i<=n;++i)
        for(ll j=1;j<=i;++j)
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
signed main()
{
    //freopen("1.in","r",stdin);
    n=read();k=read();m=read();
    scanf("%s",a+1);
    scanf("%s",b+1);
    for(ll i=1;i<=n;++i)
        if(a[i]==b[i])++cnt;
    get_combination();
    f[0][cnt]=1;
    for(ll i=1;i<=k;++i)
    {
        for(ll l=0;l<=n;++l)//枚举上一次状态
        {
            if(!f[i-1][l])continue;
            for(ll j=0;j<=n;++j)//枚举当前状态
            {
                if(abs(l-j)>m)continue;
                ll s1=abs(l-j);
                if((m-s1)&1)continue;
                ll s2=(m-s1)>>1;
                ll rest=n-l;
                if(j>=l)
                {
                    if(s1+s2<=rest)f[i][j]=(f[i][j]+((f[i-1][l]*c[rest][s1+s2]%mod)*c[l][s2])%mod)%mod;
                }
                else if(s1+s2<=l)f[i][j]=(f[i][j]+((f[i-1][l]*c[rest][s2]%mod)*c[l][s1+s2])%mod)%mod;
            }
        }
    }
    printf("%lld\n",f[k][n]);
    return 0;
}
View Code

LINK:系统设计想了2h都没思路的题目XR-round系列 才知道有这种题目的存在,好难想啊。

当时比赛的时候都没能想出来 一棵有根树每次指定一个节点开始沿着m这个序列向下跑问停止时到达哪个节点了?显然的nQ暴力,除此之外也就没有什么了。

但是呢$n,Q<=200000$这显然是不能过的,考虑一下正解?离线?因为还带修改所以基本上需要在线搞。

观察 修改的是什么m这个序列 我们从题目本身下手 修改这个序列是为了阻止我们暴力的干某件事情所以进行了修改。其实就算不修改我们离线之后尽管每个点都便利了一次但是最后的复杂度仍免不了是MQ的照样歇菜。

如何对于一棵树的某一条链和这个序列的l r进行快速匹配呢?我们发现了我们先对树的每一层进行标号 然后设根到某个节点x的路径上的字符串为sx那么发现了什么这个字符串最多有O(n)。

所以对于一次询问我们快速提取出l到r这个区间内的字符串再加上询问的从x出发的那个节点sx且如果存在我们就得到了目标节点的字符串信息了直接查找。

查找的话还是字符串hash比较好一点这样如果保证存在我们直接在主席树上找是否有这个值出现即可。但是如果不存在怎么办?这是一个比较严重的问题好像很难解决的样子,不过好像也是可以的考虑到答案必然是某个节点我们不妨二分一下深度 答案之上的节点我们必然也可以找到 所以显然二分是成立的。复杂度$Q(logn)^2$但是注意到一个问题带修改的m序列hash值也在变暴力一点直接线段树维护hash值结束..

但是太暴力了,写两棵树我不是很想写,看了题解 发现果然和题解差的好远,题解直接用线段树维护了hash值,然后查找直接线段树上二分省去了我那个二分的log然后判定的话直接hash表中进行判定。

真的是简洁,那么这道题就这样做即可。但是经过不懈的对拍发现了自然溢出hash发生了冲突 在这里我们很难使用挂链法因为我们就是想要尽可能的没有冲突。

采用双hash双map...(当然可能会很慢.. 还是被卡了 单模数hashwa了?我忍了 双模数hash也wa了?我也忍了..不写了回来请教一下举办这场比赛的人再说.

于是我开大了进制数 书上说 131 13331 这两个进制数冲突率极低 但是这两个我都试了一直wa 改模数也是wa 开大到1000000007 发现就不wa了。很开心。

但是T了... 常数实在是太大了 线段树上二分 + map 两个常数极大的东西 开o2也不管用。

终于被我找到了方法 手动hash 关掉map .. 因为树状数组上倍增对于这道题来说实在是太难写了。我真不会quq

所以 开了拉链法 注:1000003 是一个质数 而1000007 不是质数。

//#include<bits/stdc++.h>
#include<iostream>
#include<queue>
#include<iomanip>
#include<cctype>
#include<cstdio>
#include<deque>
#include<utility>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 1000000000
#define ll long long
#define db double
#define mod 1000000007
#define ull unsigned long long
#define un unsigned
#define P 1000000007ull
#define l(p) t[p].l
#define r(p) t[p].r
#define sum(p) t[p].sum
#define ans(p) t[p].ans
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=500010,Mod=1000003;
int n,m,Q,root,rt,id,len;
ull f[MAXN],p[MAXN],ans,c,ver[Mod],ver1[Mod];
int a[MAXN],lin[Mod],nex[Mod];
vector<int>g[MAXN];
inline void add(int x,int y,ull z)
{
    ver[++len]=y;
    ver1[len]=z;
    nex[len]=lin[x];
    lin[x]=len;
}
inline void insert(ull x,int y)
{
    int w=x%Mod;
    add(w,y,x);
}
inline int find(ull x)
{
    int w=x%Mod;
    for(int i=lin[w];i;i=nex[i])
        if(ver1[i]==x)return ver[i];
    return 0;
}
struct wy
{
    int l,r;
    ull sum;
    int ans;
}t[MAXN<<2];
inline void dfs(int x)
{
    int cnt=0;
    sort(g[x].begin(),g[x].end());
    for(un int i=0;i<g[x].size();++i)
    {
        int tn=g[x][i];
        ++cnt;
        f[tn]=f[x]*P+cnt;
        insert(f[tn],tn);
        dfs(tn);
    }
}
inline void pushup(int w){sum(w)=sum(l(w))*p[ans(r(w))]+sum(r(w));}
inline void build(int &p,int l,int r)
{
    if(!p)p=++id;
    t[p].ans=(r-l+1);
    if(l==r){sum(p)=a[l];return;}
    int mid=(l+r)>>1;
    build(l(p),l,mid);
    build(r(p),mid+1,r);
    pushup(p);
}
inline int ask(int s,int l,int r,int x)
{
    if(l==r)
    {
        c=ans*p[ans(s)]+sum(s);
        if(find(c))
        {
            ans=c;
            return r;
        }
        return l-1;
    }
    int mid=(l+r)>>1;
    if(l>=x)
    {
        c=ans*p[ans(s)]+sum(s);
        if(find(c))
        {
            ans=c;
            return r;
        }
        c=ans*p[ans(l(s))]+sum(l(s));
        if(find(c))
        {
            ans=c;
            return ask(r(s),mid+1,r,x);
        }
        return ask(l(s),l,mid,x);
    }
    if(x>mid)return ask(r(s),mid+1,r,x);
    int w=ask(l(s),l,mid,x);
    if(w==mid)
    {
        c=ans*p[ans(r(s))]+sum(r(s));
        if(find(c))
        {
            ans=c;
            return r;
        }
        return ask(r(s),mid+1,r,x);
    }
    return w;
}
inline void query(int s,int l,int r,int L,int R)
{
    if(L>R)return;
    if(L<=l&&R>=r)
    {
        ans=ans*p[ans(s)]+sum(s);
        //cout<<s<<endl;
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)query(l(s),l,mid,L,R);
    if(R>mid)query(r(s),mid+1,r,L,R);
    return;
}
inline void modify(int p,int l,int r,int x,int w)
{
    if(l==r){sum(p)=w;return;}
    int mid=(l+r)>>1;
    if(x<=mid)modify(l(p),l,mid,x,w);
    if(x>mid)modify(r(p),mid+1,r,x,w);
    pushup(p);
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();Q=read();p[0]=1;
    for(int i=1;i<=n;++i)
    {
        int x=read();
        p[i]=p[i-1]*P;
        if(!x)root=i;
        else g[x].push_back(i);
    }
    dfs(root);insert(0,root);
    //for(int i=1;i<=n;++i)cout<<f[i]<<endl;
    for(int i=1;i<=m;++i)a[i]=read();
    build(rt,1,m);
    //ans=0;query(rt,1,n,1,2);
    //cout<<ans<<endl;
    for(int i=1;i<=Q;++i)
    {
        int op,l,r,x;
        op=read();
        if(op==1)
        {
            x=read();l=read();r=read();
            ans=f[x];
            int w=ask(rt,1,m,l);
            w=min(w,r);
            ans=f[x];
            query(rt,1,m,l,w);
            printf("%d\n",find(ans));
        }
        else
        {
            int y;
            x=read();y=read();
            modify(rt,1,m,x,y);
        }
    }
    return 0;
}
View Code

很开心。

LINK:古代猪文 这题好啊 SDOI 2010 三道猪的神题..果然强大。一句话题意 求 $G^{\sum_{d|n}{C(d,n)}}mod999911659$

其中C是组合数 设p=999911659 当G是p的倍数显然为0 考虑不是p的倍数的时候我们显然可以发现p是个质数 那么直接欧拉降幂Y,

降幂之后相当于指数%上p-1 现在求一个组合数%p-1即可。这个d我们可以根号n枚举 在可以承受的范围内。关键是如何求一个组合数模p-1.

p-1显然不是一个质数 不能卢卡斯定理 但是 我们 可以发现一个问题 p-1=2*3*166651943 暴力枚举166651943 发现能整除的有4679 35617 再次暴力枚举4679 和35617 发现这两个都是质数

也就是说p-1分解成了4个质数且这四个质数的次数都为1 那么 我们显然可以利用组合数模这个质数的答案然后,发现了什么其实我们答案%p-1很难搞但是答案%这四个质数答案很好搞。

也就是有了4个同余方程 且合并完之后刚好是我们的p-1 那么同余方程一解 快速幂即可。

码了一个h 写写停停还是对数论的一些定理不是太熟悉..

#//#include<bits/stdc++.h>
#include<iostream>
#include<queue>
#include<iomanip>
#include<cctype>
#include<cstdio>
#include<deque>
#include<utility>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 1000000000
#define ll long long
#define db double
#define pb push_back
#define un unsigned
using namespace std;
char *ft,*fs,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const ll p=999911659;
ll G,n,ans[5],an,xx,yy;
ll jc[5][400000],inv[5][400000];
ll y[5]={0,2,3,4679,35617};
inline ll gcd(ll a,ll b)
{
    if(!b)return a;
    return gcd(b,a%b);
}
inline ll Lucas(ll a,ll b,ll mod,ll w)
{
    if(a<b)return 0;
    if(a==b)return 1;
    if(b==0)return 1;
    if(a<mod)return ((jc[w][a]*inv[w][b])%mod*inv[w][a-b])%mod;
    return (Lucas(a%mod,b%mod,mod,w)*Lucas(a/mod,b/mod,mod,w))%mod;
}
inline ll ksm(ll b,ll pp,ll mod)
{
    ll ans=1;
    while(pp)
    {
        if(pp&1)ans=ans*b%mod;
        b=b*b%mod;
        pp=pp>>1;
    }
    return ans;
}
inline void get_ans(ll x)//求C(n,x)
{
    for(ll i=1;i<=4;++i)
        ans[i]+=Lucas(n,x,y[i],i);
}
inline void exgcd(ll a,ll b)
{
    if(!b){xx=1,yy=0;return;}
    exgcd(b,a%b);
    ll z=xx;xx=yy;yy=z-a/b*yy;
}
inline void merge()//同余方程求答案
{
    for(ll i=1;i<=4;++i)//显然通解是x=Mi*Ti*ai
    {
        ll M=(p-1)/y[i];//求解 Ti Mi*Ti=ai(mod y[i])
        exgcd(M,y[i]);
        xx=(xx%y[i]+y[i])%y[i];
        an=(an+(M*xx)%(p-1)*ans[i])%(p-1);
    }
}
inline void solve()//求G的次数ans
{
    for(ll i=1;i*i<=n;++i)
        if(n%i==0)
        {
            get_ans(i);
            if(i!=n/i)get_ans(n/i);
        }
    merge();
    return;
}
signed main()
{
    //freopen("1.in","r",stdin);
    n=read();G=read();
    if(gcd(G,p)==p){puts("0");return 0;}
    for(ll i=1;i<=4;++i)
    {
        jc[i][0]=1;
        for(ll j=1;j<y[i];++j)
            jc[i][j]=jc[i][j-1]*j%y[i];
        inv[i][y[i]-1]=ksm(jc[i][y[i]-1],y[i]-2,y[i]);
        for(ll j=y[i]-2;j>=1;--j)
            inv[i][j]=inv[i][j+1]*(j+1)%y[i];
    }
    solve();
    printf("%d\n",ksm(G,an,p));
    return 0;
}
View Code

 LINK:最短路的变形问题 其实就是随机删掉一条边在最差的情况下1 到 n的最短路 显然我们任意求出一条最短路 然后如果删掉的不是最短路上的边那么答案不变。

考虑 枚举删掉最短路的边 然后再跑最短路 每次取max即可。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<stack>
#include<set>
#include<bitset>
#include<vector>
#include<map>
#include<deque>
#include<utility>
#define INF 1000000000
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define mp make_pair
#define S second
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=1010,maxn=1000010;
int n,m,len=1,top,mark,ans;
int pre[MAXN],s[MAXN],dis[MAXN],vis[MAXN];
int lin[MAXN],ver[maxn<<1],nex[maxn<<1],e[maxn<<1];
priority_queue<pair<int,int> > q;
inline void add(int x,int y,int z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline void dij()
{
    for(int i=1;i<=n;++i)vis[i]=0,dis[i]=INF;
    dis[1]=0;q.push(mp(0,1));
    while(q.size())
    {
        int x=q.top().S;q.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(int i=lin[x];i;i=nex[i])
        {
            if(i==mark||((i^1)==mark))continue;
            int tn=ver[i];
            if(dis[tn]>dis[x]+e[i])
            {
                dis[tn]=dis[x]+e[i];
                pre[tn]=i;
                q.push(mp(-dis[tn],tn));
            }
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int x,y,z;
        x=read();y=read();z=read();
        add(x,y,z);add(y,x,z);
    }
    dij();
    int w=n;
    while(w!=1)
    {
        s[++top]=pre[w];
        w=ver[pre[w]^1];
    }
    for(int i=1;i<=top;++i)
    {
        mark=s[i];
        dij();
        ans=max(ans,dis[n]);
    }
    printf("%d\n",ans);
    return 0;
}
View Code

但是复杂度不够优秀 n^2logn 我觉得不太好...考虑更优秀的做法。

还是上一道高难度的题目比较有说服力:LINK:道路堵塞 每次都去掉一条边 然后询问 最短路是多长。

n<100000 m<200000 这个范围 我们标记最短路如果询问不是最短路上的边我们就不跑是的话我们就再跑一边这样的复杂度显然还是上述的复杂度。。

考虑优化..本人不会先咕了。

我肯定 咕不超过两个月这个问题激怒了 我 学习了一种比较假的做法(毕竟spfa已经死了 在升级之前也不可能复活了。

具体 做法是这样的 题目中已经给出了最短路 首先暴力显然是 暴力删边 然后暴力跑。复杂度过高。

考虑动态的spfa 什么也就是我们只spfa一次 求出所有的 答案。虽然复杂度最坏nm 但是 显然 在一些情况下出题人还是会让spfa 复生的。

首先把所有的边都ban掉 求出 $g_i$ 表示最短路上的第i个点到终点的最短路。 先从1跑一边spfa但 最短路上的边都不能用。

然后 一旦跑到最短路上的某一点 那么 就可能是答案 $dis_i+g_i$ 这样的话就成为了答案的候选项 加入堆中。

考虑 不断启用 最短路上边 但是同时也不能用当前的最短路 也就是当前的最短路之前的点都是不合法的所以pop 。

总复杂度 1次spfa的操作 nt 吧 /cy

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000010
#define ll long long
#define mp(x,y) make_pair(x,y)
#define un unsigned
#define db double
#define EPS 1e-5
#define pii pair<int,int>
#define mk make_pair
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=200010;
int n,m,len,L,t,h;
int pos[MAXN],dis[MAXN],q[MAXN<<4];
int w[MAXN],vis[MAXN],g[MAXN],id[MAXN],mark[MAXN];
int lin[MAXN],nex[MAXN],ver[MAXN],e[MAXN];
priority_queue<pii> s;
inline void add(int x,int y,int z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline void spfa(int xx)
{
    t=h=0;
    q[++t]=xx;vis[xx]=1;
    while(h++<t)
    {
        int x=q[h];vis[x]=0;
        for(int i=lin[x];i;i=nex[i])
        {
            if(mark[i])continue;
            int tn=ver[i];
            if(dis[tn]>dis[x]+e[i])
            {
                dis[tn]=dis[x]+e[i];
                if(id[tn])s.push(mk(-(dis[tn]+g[id[tn]]),id[tn]));
                else if(!vis[tn])q[++t]=vis[tn]=tn;
            }
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();L=read();
    for(int i=1;i<=m;++i)
    {
        int x,y;
        x=read();y=read();
        add(x,y,read());
    }
    for(int i=1;i<=L;++i)
    {
        w[i]=read();
        mark[w[i]]=1;
        pos[i+1]=ver[w[i]];
        id[ver[w[i]]]=i+1;
    }
    memset(dis,0x3f,sizeof(dis));
    id[1]=pos[1]=1;
    for(int i=L;i>=1;--i)g[i]=g[i+1]+e[w[i]];
    dis[1]=0;spfa(1);
    for(int i=1;i<=L;++i)
    {
        while(s.size()&&s.top().second<=i)s.pop();
        if(!s.size())puts("-1");
        else printf("%d\n",-s.top().first);
        dis[pos[i+1]]=dis[pos[i]]+e[w[i]];
        spfa(pos[i+1]);
    }
    return 0;
}
View Code

LINK:水滑梯神仙题目我想都没想出来应该怎么写QuQ。。

 清醒了一下思考了 一下 我的拓扑的思路是错误的 一条边随机的不能走,而不是一个最优解被舍弃,这样做法是不正确的 因为存在剩下的解不是最差解中的最优解。

考虑dp f[i][j]表示到了第i个点此时经过k次情况形成的最优解 仔细思考我们不知道后续的状态可能这个状态无法达到最差的状态,所以还需要更改。

不难发现 想要转移需要后续状态的加持 再设一个更清晰的状态 f[i][j] 表示到达第i个点此时还有j次不可控的情况未发生且到达终点的最优解这个显然是min max 转移我想。

显然有终点状态 f[n][0]=0;在一张DAG上直接搜即可。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 20000000000000000ll
#define ll long long
#define mp(x,y) make_pair(x,y)
#define un unsigned
#define d(i) t[i].d
#define x(i) t[i].x
#define db double
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const ll MAXN=150010;
ll n,m,k,t,h,len;
ll lin[MAXN],nex[MAXN],ver[MAXN],e[MAXN];
ll f[MAXN][15],q[MAXN],tmp[MAXN],ru[MAXN];
inline void add(ll x,ll y,ll z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline int dfs(int x,int s)
{
    if(f[x][s]>=0)return f[x][s];
    ll minn=INF,maxx=0;
    if(s>=1)
    {
        for(int i=lin[x];i;i=nex[i])
        {
            int tn=ver[i];
            dfs(tn,s-1);
            minn=min(minn,f[tn][s-1]+e[i]);
        }
    }
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        dfs(tn,s);
        maxx=max(maxx,f[tn][s]+e[i]);
    }
    f[x][s]=min(minn,maxx);
    return f[x][s];
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();k=read();
    memset(f,0xcf,sizeof(f));
    for(ll i=1;i<=m;++i)
    {
        ll x,y,z;
        x=read();y=read();z=read();
        add(x,y,z);
    }
    f[n][0]=0;
    dfs(1,k);
    printf("%lld\n",f[1][k]);
    return 0;
}
View Code

注意转移 max 套min 即可。

LINK:一道好想但是并不好写的题目 类似于奶酪的题目。

对于答案显然是二分 但是并不好写 因为check的原因 只有一堆信号塔 以及最左端和最右端 将其都话点最后判断 最左端和最右端是否相连,这个显然是不好搞得还是存在 最左端出现多个代表位置。

不妨维护某个集合的最大向左延伸 和最大向右延伸即可。然后n^2并查集 这个排序可能做不到O(n)可能存在两个点的横坐标相同所以O(n)不太好做所以就采用暴力枚举了。

所以此题 想还是很好想的做法需要斟酌一番。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 20000000000000000ll
#define ll long long
#define mp(x,y) make_pair(x,y)
#define un unsigned
#define db double
#define EPS 1e-5
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=1010;
int n,m;
struct wy
{
    int x,y;
}t[MAXN];
int f[MAXN];
db L[MAXN],R[MAXN];
inline db dis(int x,int y)
{
    return sqrt(1.0*(t[x].x-t[y].x)*(t[x].x-t[y].x)+(ll)(t[x].y-t[y].y)*(ll)(t[x].y-t[y].y));
}
inline int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);}
inline int check(db x)
{
    for(int i=1;i<=m;++i)
    {
        L[i]=t[i].x-x;
        R[i]=t[i].x+x;
        f[i]=i;
        //求最长向左延伸和最长向右延伸
    }
    for(int i=1;i<=m;++i)
        for(int j=i+1;j<=m;++j)
        {
            db w=dis(i,j);
            if(w<=x+x)
            {
                int xx=getfather(i);
                int yy=getfather(j);
                if(xx==yy)continue;
                f[xx]=yy;
                L[yy]=min(L[yy],L[xx]);
                R[yy]=max(R[yy],R[xx]);
            }
        }
    for(int i=1;i<=m;++i)
    {
        int xx=getfather(i);
        if(L[xx]<=0&&R[xx]>=n)return 1;
    }
    return 0;
}
int main()
{
    freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int x,y;
        x=read();y=read();
        t[i]=(wy){x,y};
    }
    db l=0,r=n;
    while(l+EPS<r)
    {
        db mid=(l+r)/2;
        if(check(mid))r=mid;
        else l=mid;
    }
    printf("%.2lf\n",l);
    return 0;
}
View Code

LINK:我还不太熟悉线段树的一些操作之标记永久化。

题目是很简单 操作 将一个区间内所有比Hi小的数改为Hi 区间很大 考虑线段树 可是离散之后貌似也没有什么更好的做法,考虑离散化之后暴力的搞最后统一下传懒标记但是存在一个问题我们把区间缩掉了,统计答案的时候我们不知道当前某两个点之间的的值到底是0还是存在对答案是有贡献的这显然很难维护这个东西 所以考虑动态开点。

但是我期望的做法是标记永久花 区间这么大但是一些区间是不必要的考虑 动态开点 可是这样加入懒标记之后不能pushdown 因为一旦pushdown就会下传好多的节点 新建好多节点导致时间和空间双双爆炸。

考虑标记永久化 最后 便利一下整颗树来统计答案。当然这个我也不太熟悉 自己随便写一个好了。

再三斟酌还是自己写(找不到以前的标记永久化代码了头疼...乌拉随便写的竟然过了 虽然数组开小了答案 算的有点锅。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000010
#define ll long long
#define mp(x,y) make_pair(x,y)
#define un unsigned
#define db double
#define EPS 1e-5
#define l(x) t[x].l
#define add(x) t[x].add
#define r(x) t[x].r
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=40010;
int n,maxx=1000000000;
int root,id;
ll ans;
struct wy
{
    int l,r;
    int add;
}t[MAXN*30<<1];
inline void change(int &p,int l,int r,int L,int R,int x)
{
    if(!p)p=++id;
    if(L==l&&R==r)
    {
        add(p)=max(add(p),x);
        return;
    }
    int mid=(l+r)>>1;
    if(L>mid)change(r(p),mid+1,r,L,R,x);
    else
    {
        if(R<=mid)change(l(p),l,mid,L,R,x);
        else change(l(p),l,mid,L,mid,x),change(r(p),mid+1,r,mid+1,R,x);
    }
}
inline void ask(int p,int l,int r,ll mx)//mx表示当前区间的最大值
{
    mx=mx>add(p)?mx:add(p);
    if(l==r){ans+=mx;return;}
    if(!p)
    {
        ans+=(r-l+1)*mx;
        return;
    }
    int mid=(l+r)>>1;
    ask(l(p),l,mid,mx);
    ask(r(p),mid+1,r,mx);
    return;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;++i)
    {
        int x,y,z;
        x=read();y=read()-1;z=read();
        change(root,1,maxx,x,y,z);
    }
    ask(root,1,maxx,0);
    printf("%lld\n",ans);
    return 0;
}
View Code

对于这个代码的复杂度我们显然可以看出 区间修改时复杂度为logn 又把区间分成logn段 总共有nlogn段最后统计一遍树只会统计到有值得点所以复杂度仍为nlogn.

LINK:WQS二分模板题 早就想写了。

恰好有need 条边的生成树并且使这颗生成树的权值和最小 所以考虑 dp 这玩意最小生成树 不太行了就我们从dp的方面想办法。

这里我们先不管答案怎么求设 fix 表示在前i条边中强制选择了x条边的的最小价值。 这 画一张图 发现这形成了一个凸包 考虑如何求出最优解 一个比较靠谱的做法是 用一条线来切这个凸包 关于凸包上

本人还是不会  所以 还是咕了。

LINK:同类分布 一道神仙 数位dp 好题啊 我根本就不会做QuQ

考虑暴力 直接爆搜 考虑dp 我们发现模数很烦 因为我们只有分配数字到最后才能知道模数是多少 不妨我们直接暴力一点枚举模数。

然后对于 就可以直接设计状态 $f_{i,j,k}$ 表示到了第i位 此时 余数是j 和位 k的方案数。

这样就可以dp下去了 复杂度 大概为 1e8左右 不过值得注意的是 mark标记的维护。即最高位的限制。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000010
#define ll long long
#define mp(x,y) make_pair(x,y)
#define un unsigned
#define db double
#define EPS 1e-5
#define pii pair<ll,ll>
#define mk make_pair
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=9*20+1,maxn=20;
ll f[maxn][MAXN][MAXN];//f[i][j][k]表示到了第i位 此时余数是j 和为k的方案数
ll n,m,ans,cnt,top,mod;
ll b[maxn];
inline ll dfs(ll pos,ll res,ll sum,ll mark)//mark 表示是否 有最高位的限制
{
    if(!mark&&f[pos][res][sum]!=-1)return f[pos][res][sum];
    if(!pos)
    {
        if(!res&&sum==mod)f[pos][res][sum]=1;
        else f[pos][res][sum]=0;
        return f[pos][res][sum];
    }
    ll ans=0,maxx=mark?b[pos]:9;
    for(int i=0;i<=maxx;++i)
    {
        if(i==maxx)ans+=dfs(pos-1,(res*10+i)%mod,sum+i,mark&1);
        else ans+=dfs(pos-1,(res*10+i)%mod,sum+i,0);
    }
    if(!mark)f[pos][res][sum]=ans;
    return ans;
}
inline ll slove(ll x)
{
    top=0;ll cnt=0;
    while(x)
    {
        b[++top]=x%10;
        x=x/10;
    }
    for(int i=1;i<=top*9;++i)
    {
        memset(f,-1,sizeof(f));
        mod=i;
        cnt=cnt+dfs(top,0,0,1);
    }
    return cnt;
}
signed main()
{
    //freopen("1.in","r",stdin);
    m=read();n=read();
    ans=slove(n);
    cnt=slove(m-1);
    printf("%lld\n",ans-cnt);
    return 0;
}
View Code

 LINK:末日的传说 想了几个月终于觉得自己可以写这道题了。 ~~这可能是我咕的最久的题目了~~

 早就有了一个初步的想法了 但是后续的细节最近才想出来 我是做了旅行的加强版才对字典序理解的更加深刻 当然 旅行那道题自己思考出来之后深刻理解了什么叫做字典序。

当前能取最优就不要让当前较优后面更优 换句话说 当前只需要取到最优的决策即可 和后面的决策无关。

所以 考虑构造一个序列字典序最小且逆序对数有m个 我们可以一位一位考虑这个做法 第一位能取1么 这得看后面的数字组成的逆序对数能否大于m了。

如果大于m的话当前显然 可以取到1 不行的话那么1必然要放到当前这个位置之后考虑接下来放的数字必然大于1 那么此时必然形成了一个逆序对 --m即可 一直寻找到这样的数字。不断做下去即可。

可以发现复杂度是n^2的当然过不了这道题 考虑加快寻找这个过程 显然主席树上二分即可 nlogn 不过代码复杂度过高 考虑树状数组+二分 所以以n(logn)^2的复杂度即可通过本题。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000010
#define ll long long
#define mp(x,y) make_pair(x,y)
#define un unsigned
#define db double
#define EPS 1e-5
#define pii pair<ll,ll>
#define mk make_pair
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=50010;
int n,m;
int vis[MAXN];
int sum[MAXN],ans[MAXN];
int c[MAXN];
inline void add(int x,int y)
{
    while(x<=n)
    {
        c[x]+=y;
        x+=x&(-x);
    }
}
inline int ask(int x)
{
    int cnt=0;
    while(x)
    {
        cnt+=c[x];
        x-=x&(-x);
    }
    return cnt;
}
inline int calc(int x)
{
    int l=1,r=n;
    while(l+1<r)
    {
        int mid=(l+r)>>1;
        if(ask(mid)>=x)r=mid;
        else l=mid;
    }
    if(ask(l)>=x)return l;
    return r;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;++i)sum[i]=(ll)i*(i-1)/2,add(i,1);
    for(int i=1;i<=n;++i)
    {
        int res=n-i;//除了当前这个数字之后还有多少位
        int cnt=1,ww;
        ww=max(0,m-sum[res]);
        cnt+=ww;
        int w=calc(cnt);
        ans[i]=w;
        add(w,-1);
        m=m-ww;
    }
    for(int i=1;i<=n;++i)printf("%d ",ans[i]);
    return 0;
}
View Code

当然 上述做法还不够优秀 显然还有更优的做法 还是考虑上述的过程 1放不到这个位置之后我们还是想让当前数字取到最优 那么想要取到最优那么m就应该尽量小 所以1放到最后一个位置m减小的幅度会很大 所以根据贪心的来选直接把1放到最后考虑下一个数字即可。

思考一下贪心的原则 可以发现这样是正确的 当然也有一个比较严谨的证明 可以自行脑补。复杂度O(n)

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000010
#define ll long long
#define mp(x,y) make_pair(x,y)
#define un unsigned
#define db double
#define EPS 1e-5
#define pii pair<ll,ll>
#define mk make_pair
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=50010;
int n,m,st,en;
int vis[MAXN];
int sum[MAXN],ans[MAXN];
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();en=n+1;
    for(int i=1;i<=n;++i)sum[i]=(ll)i*(i-1)/2;
    for(int i=1;i<=n;++i)
    {
        int res=n-i;//除了当前这个数字之后还有多少位
        if(sum[res]>=m)ans[++st]=i;
        else m-=res,ans[--en]=i;
    }
    for(int i=1;i<=n;++i)printf("%d ",ans[i]);
    return 0;
}
View Code

 LINK:区间第k小 

1 直接sort 2 归并树 3 主席树。 第一种 nmlogn 无脑的东西 无聊 第二种 利用归并排序+二分+lower_bound无聊的方法 mlogn*logn*logn 这种做法虽然较劣 但是还是 归并树的思想还是要学习的。

3 主席树 比较经典的做法不再赘述。代码是 主席树 但是归并树这个东西真的很少见。可能会用到的东西。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#define INF 1000000000
#define ll long long
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define l(p) t[p].l
#define r(p) t[p].r
#define sum(p) t[p].sum
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=100010;
int n,m,id,minn=INF,maxx=-INF;
int a[MAXN],root[MAXN];
struct wy
{
    int l,r;
    int sum;
}t[MAXN*30<<1];
inline void insert(int &p,int last,int l,int r,int x)
{
    p=++id;t[p]=t[last];
    if(l==r){++sum(p);return;}
    int mid=(l+r)>>1;
    if(x<=mid)insert(l(p),l(last),l,mid,x);
    else insert(r(p),r(last),mid+1,r,x);
    sum(p)=sum(l(p))+sum(r(p));
}
inline int ask(int p,int last,int l,int r,int k)
{
    if(l==r)return l;
    int mid=(l+r)>>1;
    int v=sum(l(p))-sum(l(last));
    if(v>=k)return ask(l(p),l(last),l,mid,k);
    return ask(r(p),r(last),mid+1,r,k-v);
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        minn=min(minn,a[i]);
        maxx=max(maxx,a[i]);
    }
    for(int i=1;i<=n;++i)insert(root[i],root[i-1],minn,maxx,a[i]);
    for(int i=1;i<=m;++i)
    {
        int x,y,z;
        x=read();y=read();z=read();
        printf("%d\n",ask(root[y],root[x-1],minn,maxx,z));
    }
    return 0;
}
View Code

 LINK:应该是二分的题目 想了一下觉得挺有意思的 跟以前见过的模型差不多 总结一下。

一个序列中 求出一段连续子序列使其 平均数最大 ,有点脑残的问题 显然其中最大的那个数独自成为一个子序列就会使 答案最优 考虑 一个限制长度需要大于k 这就有一点点复杂了...

显然 遇到这种问题不防把式子写出来 $max{\frac{\sum_{a_i}}{w}}$ 考虑如何求出答案 显然是分数规划问题 不妨二分一下答案。考虑check。

在答案是mid时可能不存在这样的序列!但此时考虑如果存在最优解ans ans的长度一定是>=k的 所以 显然必然存在一个长度大于k的序列其累加和大于0 所以对于每一个右端点寻找到一个最小的左端点即可。这样显然可以check答案。所以就解决了问题。我找到了题目的链接!

LINK:寻找段落 code:

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#define INF 1000000000
#define ll long long
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define db double
#define EPS 1e-5
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=100010;
int n;
int S,T;
int a[MAXN];
int minn=INF,maxx=-INF;
db b[MAXN];
int q[MAXN],h,t;
inline int check(db x)
{
    db v=-INF;
    h=1;t=0;
    for(int i=1;i<S;++i)b[i]=a[i]-x+b[i-1];
    q[++t]=0;
    for(int i=S;i<=n;++i)
    {
        b[i]=a[i]-x+b[i-1];
        while(h<=t&&q[h]<i-T)++h;
        v=max(v,b[i]-b[q[h]]);
        while(h<=t&&b[q[t]]>=b[i-S+1])--t;
        q[++t]=i-S+1;
    }
    if(v<0)return 0;
    return 1;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();S=read();T=read();
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        maxx=max(maxx,a[i]);
        minn=min(minn,a[i]);
    }
    db l=minn,r=maxx;
    while(l+EPS<r)
    {
        db mid=(l+r)/2;;
        if(check(mid))l=mid;
        else r=mid;
    }
    printf("%.3lf",l);
    return 0;
}
View Code

这道题目 想要让我们去掉中间的连续子序列 使得剩下的平均奶产量最小,贪心显然无法解决这个问题。

还是二分 但是 考虑一个问题上面两道题目是否等价的问题 一个两边平均值最小 一个中间平均值最大  遗憾的是随便出个数据即可说明显然是不等价的问题。

那么 我苦思冥想一中午 发现 这不是裸的二分么 我sb了...

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#define INF 1000000000
#define ll long long
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define db double
#define EPS 1e-5
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=100010;
int n;
int a[MAXN];
int minn=INF,maxx=-INF;
db b[MAXN];
int q[MAXN],h,t;
inline int check(db x)
{
    db v=INF;
    h=1;t=0;
    for(int i=n;i>=1;--i)b[i]=a[i]-x+b[i+1];
    for(int i=3;i<=n;++i)
    {
        while(h<=t&&b[q[t]]>=b[i])--t;
        q[++t]=i;
    }
    db cnt=0;
    for(int i=1;i<=n-2;++i)
    {
        cnt+=a[i]-x;
        while(h<=t&&q[h]<=i+1)++h;
        v=min(v,cnt+b[q[h]]);
    }
    if(v>0)return 0;
    return 1;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;++i)
    {
        a[i]=read();
        maxx=max(maxx,a[i]);
        minn=min(minn,a[i]);
    }
    db l=minn,r=maxx;
    while(l+EPS<r)
    {
        db mid=(l+r)/2;;
        if(check(mid))r=mid;
        else l=mid;
    }
    printf("%.3lf",l);
    return 0;
}
View Code

 LINK:最大正方形加强版 每次都ban掉一个点求最大的正方形 前一次ban 对后一次有影响的那种。

怎么做 首先求最大的正方形 有两种方法 悬线法 或者直接 dp 可惜 这两种方法都不支持动态的在图上更改 一旦更改就是n^2的 复杂度 n^2k 这显然不是我们期望承受的复杂度。

考虑 怎么解决这个问题。 dp一旦修改死死的n^2 考虑悬线法求答案 动态ban点考虑动态加点ban点对答案影响太大 一旦ban掉一个答案 还得重新找一个答案很难操作 考虑倒序 加点。

然后 更新一下向左延伸和向右延伸的最大长度 求出以当前这个点为基准的最大正方形看能否更新答案即可整个复杂度为nk+n^2

//#include<bits/stdc++.h>
#include<iostream>
#include<queue>
#include<iomanip>
#include<cctype>
#include<cstdio>
#include<deque>
#include<utility>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
#define INF 1000000000
#define ll long long
#define db double
#define mod 1000000007
#define ull unsigned long long
#define un unsigned
#define P 1000000007ull
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=2010;
int n,m,k,cnt;
char a[MAXN][MAXN];
struct wy{int x,y;}t[MAXN];
int l[MAXN][MAXN],r[MAXN][MAXN];
int f[MAXN][MAXN],ans[MAXN];
int L,R,q[MAXN],w[MAXN];
inline int check(int d,int y)
{
    L=1;R=0;
    for(int i=1;i<=n;++i)
    {
        while(L<=R&&l[q[R]][y]>=l[i][y])--R;
        q[++R]=i;
        while(L<=R&&q[L]<=i-d)++L;
        w[i]=l[q[L]][y];
    }
    L=1;R=0;
    for(int i=1;i<=n;++i)
    {
        while(L<=R&&r[q[R]][y]>=r[i][y])--R;
        q[++R]=i;
        while(L<=R&&q[L]<=i-d)++L;
        w[i]+=r[q[L]][y]-1;
    }
    for(int i=d;i<=n;++i)if(w[i]>=d)return 1;
    return 0;
} 
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();k=read();
    for(int i=1;i<=n;++i)scanf("%s",a[i]+1);
    for(int i=1;i<=k;++i)
    {
        int x,y;
        x=read();y=read();
        t[i]=(wy){x,y};
        a[x][y]='X';
    }
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            if(a[i][j]=='X')
            {
                l[i][j]=0;f[i][j]=0;
                continue;
            }
            l[i][j]=l[i][j-1]+1;
            f[i][j]=min(f[i][j-1],min(f[i-1][j-1],f[i-1][j]))+1;
            cnt=max(cnt,f[i][j]);
        }
        for(int j=m;j;--j)
        {
            if(a[i][j]=='X')r[i][j]=0;
            else r[i][j]=r[i][j+1]+1;
        }
    }
    for(int i=k-1;i;--i)
    {
        ans[i+1]=cnt;
        int x=t[i+1].x;
        int y=t[i+1].y;
        a[x][y]='.';
        for(int j=1;j<=m;++j)l[x][j]=a[x][j]=='X'?0:l[x][j-1]+1;
        for(int j=m;j;--j)r[x][j]=a[x][j]=='X'?0:r[x][j+1]+1;
        while(check(cnt+1,y))++cnt;
    }
    ans[1]=cnt;
    for(int i=1;i<=k;++i)printf("%d\n",ans[i]);
    return 0;
}
View Code

 LINK:统计距离问题非常巧妙的题目。 

初看这道题觉得很难做只能枚举坐标然后 前缀和什么的显然过不了 。考虑转换 画在图上一个点的可控范围是一个类似菱形的正方形 这种很难搞吧 我们扫描线维护不了这么不规则的东西。

考虑切换成正方形! 怎么切换?发现转契比雪夫距离就变成了一个正方形的问题了 神奇 。然后呢我们就是用一个边长为2k的正方形来 圈一些点 使其代价最大。

(窗口的星星可还行  然后就是把每个点当成 一个正方形扫描线扫一下即可。

注意离散化 要不就平移坐标 但是动态开点标记永久化好像不太行的样子维护区间最值我也不太会。因为平移坐标暴力线段树了 所以空间消耗大会T。

最后还是离散了一下。

//单个局面偶数必败
//觉得单个局面除了1 剩下也是必败 直接异或好了
//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000000
#define ll long long
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define RI register ll
#define db double
#define pii pair<ll,ll>
#define mk make_pair
#define l(p) t[p].l
#define r(p) t[p].r
#define sum(p) t[p].sum
#define tag(p) t[p].tag
#define zz p<<1
#define yy p<<1|1
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=100010<<1;
int n,k,cnt,top,ans;
struct wy
{
    int l,r;
    int tag;
    int sum;
}t[MAXN<<2],w[MAXN];
int b[MAXN];
inline int cmp(wy a,wy b){return a.l<b.l;}
inline void build(int p,int l,int r)
{
    l(p)=l;r(p)=r;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(zz,l,mid);
    build(yy,mid+1,r);
}
inline void pushdown(int p)
{
    sum(zz)+=tag(p);
    sum(yy)+=tag(p);
    tag(zz)+=tag(p);
    tag(yy)+=tag(p);
    tag(p)=0;
    return;
}
inline void change(int p,int l,int r,int x)
{
    if(l<=l(p)&&r>=r(p))
    {
        sum(p)+=x;
        tag(p)+=x;
        return;
    }
    int mid=(l(p)+r(p))>>1;
    if(tag(p))pushdown(p);
    if(l<=mid)change(zz,l,r,x);
    if(r>mid)change(yy,l,r,x);
    sum(p)=max(sum(zz),sum(yy));
}
inline void discrete()
{
    sort(b+1,b+1+cnt);
    for(ll i=1;i<=cnt;++i)if(i==1||b[i]!=b[i-1])b[++top]=b[i];
    for(ll i=1;i<=cnt;++i)
    {
        w[i].r=lower_bound(b+1,b+1+top,w[i].r)-b;
        w[i].tag=lower_bound(b+1,b+1+top,w[i].tag)-b;
    }
}
int main()
{
    freopen("1.in","r",stdin);
    n=read();k=read()<<1;
    for(int i=1;i<=n;++i)
    {
        int x,y,z;
        z=read();x=read();y=read();
        w[++cnt].l=x+y;b[cnt]=w[cnt].r=x-y;
        w[cnt].tag=x-y+k;w[cnt].sum=z;
        w[++cnt].l=x+y+k+1;w[cnt].r=x-y;
        w[cnt].tag=x-y+k;w[cnt].sum=-z;
        b[cnt]=x-y+k;
    }
    discrete();
    sort(w+1,w+1+cnt,cmp);
    build(1,1,top);
    for(int i=1;i<=cnt;++i)
    {
        change(1,w[i].r,w[i].tag,w[i].sum);
        //cout<<w[i].l<<endl;
        //cout<<w[i].sum<<' '<<sum(1)<<endl;
        while(w[i].l==w[i+1].l&&i+1<=cnt)
        {
            ++i;
            change(1,w[i].r,w[i].tag,w[i].sum);
            //cout<<w[i].sum<<' '<<sum(1)<<' '<<endl;
        }
        ans=max(ans,sum(1));
    }
    printf("%d\n",ans);
    return 0;
}
View Code

LINK:CTSC2018 mix juice 

这题看起来很神仙 一些约束 :有n种果汁 但是某种果汁只有 li 升 而每升价值为pi 美味度为di

现在要兑一瓶大果汁了 这瓶汁总价格不能超过gi 内部至少有Li升果汁。问所可以兑出来的大果汁的最高美味度。最高美味度的定义也有了所有加进去果汁的最小的美味度。

gi Li<=10^18 显然 暴力枚举 然后check 复杂度 的话加到set里直接二分?优美一点线段树上二分的姿势比较好,可以维护区间和。

说道这里我们观察到了 一点答案是具有单调性的干嘛要暴力枚举 直接二分一个答案然后再利用线段树上二分来check。

不过这里线段树要可持久化吧?可持久话的线段树支持上面二分么?应该支持复杂度mlognlogn好像能过的样子口胡完了看一波题解。

口胡出正解了 开心,当然整体二分也是可以的。这里给主席树。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define INF 1000000000000000010ll
#define ll long long
#define l(x) w[x].l
#define r(x) w[x].r
#define sum(x) w[x].sum
#define v(x) w[x].v
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=100010;
int n,m,maxx,cnt;
int root[MAXN];
ll xx,yy,ans,vy;
struct wy{int d,p,l;}t[MAXN];
struct jl{int l,r;ll sum,v;}w[MAXN*30];
inline int cmp(wy a,wy b){return a.d>b.d;}
inline void insert(int &p,int last,int l,int r,ll x,ll xx)
{
    p=++cnt;
    w[p]=w[last];
    if(l==r)
    {
        sum(p)+=x*xx;v(p)+=xx;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)insert(l(p),l(last),l,mid,x,xx);
    else insert(r(p),r(last),mid+1,r,x,xx);
    sum(p)=sum(l(p))+sum(r(p));
    v(p)=v(l(p))+v(r(p));
}
inline void calc(int p,int l,int r)
{
    if(v(p)<vy){ans=INF;return;}
    if(l==r)
    {
        ans+=vy*l;
        return;
    }
    int mid=(l+r)>>1;
    if(v(l(p))>=vy)calc(l(p),l,mid);
    else
    {
        ans+=sum(l(p));
        vy-=v(l(p));
        calc(r(p),mid+1,r);
    }
}
inline int check(int x)//线段树上二分一个数量
{
    vy=yy;ans=0;//sum统计数量ans统计答案
    calc(root[x],1,maxx);
    return ans<=xx;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;++i)
    {
        int x,y,z;
        x=read();y=read();z=read();
        t[i]=(wy){x,y,z};
        maxx=max(maxx,y);
    }
    sort(t+1,t+1+n,cmp);
    for(int i=1;i<=n;++i)insert(root[i],root[i-1],1,maxx,t[i].p,t[i].l);
    for(int i=1;i<=m;++i)
    {
        xx=read();yy=read();
        int L=1,R=n+1;//在单调递增的序列上进行二分
        while(L+1<R)
        {
            int mid=(L+R)>>1;
            if(check(mid))R=mid;
            else L=mid;
        }
        if(check(L))printf("%d\n",t[L].d);
        else 
        {
            if(R==n+1)puts("-1");
            else printf("%d\n",t[R].d);
        }
    }
    return 0;
}
View Code

整体二分的做法:考虑逐个二分太慢了直接整体二分每个操作被分到logn层然后 怎么check线段树直接check但是这里可以使不必要的 整体二分的优势在于check是有序的。

也就是说前面的操作后面也是可以使用的。树状数组+二分来搞复杂度nlog^3 很没必要的操作用可持久化线段树还是nlog^2的这样又体现不出来整体二分的优势了 所以这道题就这样做就行了。不过可以不用可持久化主席树。

动态插入 但是没必要了 复杂度仍为nlogn^2 和上面的及其类似qwq。

LINK:HNOI2007梦幻岛宝珠 

看起来是一个背包 但是容量太大了很难搞。

考察问题的特异性 每个wi 可以写成 a*2^b 的形式。这里我们不能将其写成一个二进制表示的价值 这样没有很大的意义也没有必要因为对于一个普通的背包我们也可以这样做 所以这样做会失去其本来的特异性。

这样的话考虑按位背包 这个可以实

 

LINK:SDOI2008 沙拉公主的困惑 

这道题 原本想着暴力 后来啊 想了一下 发现是一个类似于欧拉函数的东西 显然我们把m的质因子抽出来然后搞一下 最后答案是$\frac{n!\cdot{phi(m)}}{m!}$

这样的话 关键点 发现m中可能含有R这个质数我们要 约分一下 约分只需要一次所以在预处理的时候直接约好分 然后 判断一下不能约的情况即可。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=10000010;
ll T,n,m,mod,cnt,maxx=10000000;
int p[MAXN],v[MAXN];
int  fac[MAXN],b[MAXN],a[MAXN];
inline ll fast_pow(ll b,ll p)
{
    ll ans=1;
    while(p)
    {
        if(p&1)ans=ans*b%mod;
        p=p>>1;
        b=b*b%mod;
    }
    return ans;
}
inline void prepare()
{
    a[1]=fac[1]=b[1]=1;
    for(int i=2;i<=maxx;++i)
    {
        if(i!=mod)fac[i]=(ll)fac[i-1]*i%mod;
        else fac[i]=fac[i-1];
        b[i]=b[i-1];a[i]=a[i-1];
        if(!v[i])
        {
            v[i]=i;p[++cnt]=i;
            a[i]=(ll)a[i]*(i-1)%mod;
            if(i!=mod)b[i]=(ll)b[i]*i%mod;
        }
        for(int j=1;j<=cnt;++j)
        {
            if(i>maxx/p[j]||p[j]>v[i])break;
            v[i*p[j]]=p[j];
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    //考虑分两种情况进行讨论
    T=read();mod=read();prepare();
    while(T--)
    {
        n=read();m=read();
        if(n>mod&&m<mod)puts("0");
        else printf("%lld\n",(ll)fac[n]*fast_pow(b[m],mod-2)%mod*a[m]%mod);
    }
    return 0;
}
View Code

LINK:智商题政治划分 嫌智商太低可以来做一下此题 简直是送命题。

我做了2h 感觉完全就是在浪费时间 题目大意a1 条直线 a2个圆 a3个三角形 a4个正方形 a5个五边形...

这些图形最多能把一个平面分成多少块?

trick 1 :除了直线无限长 剩下的都是可大可小。

trick 2:感觉非常不可做。

首先考虑一下 n==1时 直线为 1 2 3 4 5 时 分别划分出了 2 4 7 11 16 个平面。

如果 有a1 条直线那么 有这显然是一个等差数列还是可以观察出来的。a1*(a1+1)/2+1;

圆 这个玩意很特殊 为 1 2 3 4 5 时 分别画出 2 4 8 14 显然是一个二介多项式 a2*(a-1)+2;

但是 圆和直线在一起 还会诞生一些东西 一个直线把圆分成两部分的话就又多了a1*a2了

那么剩下的就好推了 除了圆特殊。

一个正n变形怎么搞 显然 n和直线诞生出两个贡献和一个圆诞生出n个贡献和一个三角形诞生出6个贡献 和一个n-1变形诞生出2*(n-1)的贡献。

还有各自之间也有贡献 2n这样累加一下即可。

填坑了鬼知道我为什么非得把这道题给补了?反正细节贼ex 有些地方我到现在还没有考虑的非常的清楚 差评qwq。

存在一些trick是用sum算端点的时候其实有的时候把已经分割过的局面又再次分割了 总之 冥冥之中形成了抵消 出题人的题解写的也不是很清楚总之这道题给差评.

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define INF 1000000000
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define EPS 1e-8
#define mod 1000000007
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const ll MAXN=110,maxn=MAXN*MAXN;
ll n;
ll sum,ans,ww,flag;
signed main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(ll i=1;i<=n;++i)
    {    
        ll x=read();
        if(!x)continue;
        flag=1;
        if(i==1)
        {
            if(x)ans=1+x*(x+1)/2;
            ans%=mod;
            sum+=x*2;
        }
        if(i==2)
        {
            ans+=sum*x;
            if(!sum)ans+=2;
            ans+=x*(x-1);
            ans%=mod;
            ww=x;
        }
        if(i>2)
        {
            ans+=sum*x%mod;
            ans+=ww*x%mod*i*2%mod;
            ans%=mod;
            ans+=i*x*(x-1)%mod;
            if(!sum&&!ww)ans+=2;
            sum+=x*2*i%mod;
            sum%=mod;ans%=mod;
        }
    }
    printf("%lld\n",flag?ans:1);
    return 0;
}
View Code

LINK:传球游戏我没想出来的dp 

当时脑子有点乱了 可能是没休息好 然后盯着这道题自闭了半天。原来是很水的。

设f[i][j]表示当前传了i次球传到了j这个人的方案数 早就发现了n那么大无用和k有关。

显然有用的人最多2k个 然后其他的人当成一个点来看待。他们的状态转移都一样 完全可以算出一个人的状态转移然后 *人数即可。

考虑转移 m*k*k 考虑优化。浅显的思路是前缀和减掉不合法 那么在每一轮减掉的次数为k 这样复杂度就降到了mk。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define mod 998244353
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=50010<<1;
ll n,m,k,cnt,num,c,len,u;
ll a[MAXN],b[MAXN];
ll f[2][MAXN];
struct wy{ll x,y;}t[MAXN];
ll lin[MAXN],ver[MAXN],nex[MAXN];
inline void add(ll x,ll y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
inline void discrete()
{
    sort(a+1,a+1+cnt);
    for(ll i=1;i<=cnt;++i)if(i==1||a[i]!=a[i-1])b[++num]=a[i];
    for(ll i=1;i<=k;++i)
    {
        if(!t[i].x)continue;
        t[i].x=lower_bound(b+1,b+1+num,t[i].x)-b;
        t[i].y=lower_bound(b+1,b+1+num,t[i].y)-b;
        add(t[i].y,t[i].x);
    }
}
int main()
{
    freopen("1.in","r",stdin);
    n=read();m=read();k=read();
    for(ll i=1;i<=k;++i)
    {
        ll x,y;
        x=read();y=read();
        if(x==y)continue;
        t[i].x=x;t[i].y=y;
        a[++cnt]=x;a[++cnt]=y;
    }
    a[++cnt]=1;
    discrete();c=n-num;
    ++num;f[u][1]=1;
    for(ll i=1;i<=m;++i)
    {
        ll ans=0;u=u^1;
        for(ll j=1;j<=num;++j)ans=(ans+f[u^1][j])%mod;
        for(ll j=1;j<num;++j)
        {
            f[u][j]=ans;
            for(ll v=lin[j];v;v=nex[v])
            {
                ll tn=ver[v];
                f[u][j]=(f[u][j]-f[u^1][tn])%mod;
            }
            f[u][j]=(f[u][j]-f[u^1][j])%mod;
        }
        f[u][num]=(ans*c%mod-f[u^1][num])%mod;
    }
    printf("%d\n",(f[u][1]+mod)%mod);
    return 0;
}
View Code

当时脑子怎么直接抽了 ...看来还是得多动手写写在脑中光想没用 且不要干一些不和实际的事情 如打表 该打的时候不打 不该打的时候开始打表浪费时间了。

LINK:super GCDDDDDD... 

两个高精的数字进行GCD 显然 我们无法使用辗转相除法 高静取模太过困难我至此都没写过。

考虑更相减损术 这样 有(a,b)=(b,a) 所以接下来我假设a>b 那么有(a,b)=(a-b,b);

证明?显然吧什么时候终止?当a==b时 或者b=0时说明了 a此时就是gcd.这样做的原因 我们在把两数逐渐缩小 两数相等的时候说明了 此时 a 和 b就是他们的gcd 。自证不难。

复杂度?发现这个复杂度非常非常的不科学如果直接硬上更相减损术的话 我们还有一个比较显然的结果是 (a,b) 当a b都为偶数的时候 (a,b)=2(a/2,b/2);

那一奇一偶呢?显然的是 偶数中的那个2绝对不是最大公约数中的一个 除掉就好了。假设高静的位数为n。

我们每次这样搞 出现一次高静减法的时候 也就是两个都是奇数的时候 减过后一定有 一个数会/2的 所以复杂度为 nlog。

当然gcd的复杂度 为log 关于其证明是 取模时对b取模的原因 a一定会掉一半 所以复杂度为logn 这里有一个推论是 辗转相除法面临的最坏情况是斐波那契数列的相邻两项

为什么?读者自证不难 (其实我也不会证明。 这样这道题就可以以nlog的复杂度解决了。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 1000000000000000000ll
#define ld long double
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=10010;
char c[MAXN];
int a[MAXN],b[MAXN];
int cnt;
struct wy
{
    int a[MAXN],len;
    wy friend operator -(wy s,wy g)
    {
        int ct=0;
        for(int i=1;i<=g.len;++i)
        {
            ct=max(ct,i+1);
            if(s.a[i]>=g.a[i])s.a[i]-=g.a[i];
            else
            {
                while(!s.a[ct])++ct;
                --s.a[ct];
                for(int j=ct-1;j>i;--j)s.a[j]=9;
                s.a[i]+=10-g.a[i];
            }
        }
        while(!s.a[s.len])--s.len;
        return s;
    }
    inline wy friend div(wy s)
    {
        int res=0;
        for(int i=s.len;i>=1;--i)
        {
            int w=res*10+s.a[i];
            res=s.a[i]&1;
            s.a[i]=w/2;
        }
        while(!s.a[s.len])--s.len;
        return s;
    }
    inline wy friend mul(wy s)
    {
        for(int i=1;i<=s.len;++i)s.a[i]*=2;
        for(int i=1;i<=s.len;++i)
        {
            s.a[i+1]+=s.a[i]/10;
            s.a[i]%=10;
        }
        while(s.a[s.len+1])++s.len;
        return s;
    }
}A,B;

inline int cmp(wy s,wy g)
{
    if(s.len>g.len)return 1;
    if(g.len>s.len)return 0;
    for(int i=s.len;i>=1;--i)
    {
        if(s.a[i]>g.a[i])return 1;
        if(g.a[i]>s.a[i])return 0;
    }
    return 2;
}
int main()
{
    //freopen("1.in","r",stdin);
    scanf("%s",c+1);A.len=strlen(c+1);
    for(int i=A.len,j=1;i>=1;--i,++j)A.a[j]=c[i]-'0';
    scanf("%s",c+1);B.len=strlen(c+1);
    for(int i=B.len,j=1;i>=1;--i,++j)B.a[j]=c[i]-'0';
    while(1)
    {
        //cout<<A.len<<' '<<B.len<<' '<<A.a[1]<<' '<<' '<<B.a[1]<<endl;
        if(!(A.a[1]&1)&&!(B.a[1]&1))
        {
            ++cnt;
            A=div(A);
            B=div(B);
            continue;
        }
        if(!(A.a[1]&1)){A=div(A);continue;}
        if(!(B.a[1]&1)){B=div(B);continue;}
        int w=cmp(A,B);
        if(w==2)break;
        if(w==1)A=A-B;
        else B=B-A;
    }
    for(int i=1;i<=cnt;++i)A=mul(A);
    for(int i=A.len;i>=1;--i)printf("%d",A.a[i]);
    return 0;
}
View Code

 LINK:区间逆序对+dp问题 本来区间的逆序对是线段树所解决不了的事情。

这个事情可以分块搞预处理$n\sqrt{n} \cdot logn$的 每次询问也是$n \sqrt{n} \cdot logn$的是不太优秀。

还有一种方法是莫队这个东西每次移动每次都要在树状数组中查询修改 复杂度$n\sqrt{n} \cdot logn$也不太行。

但值得一提的事情是 如果修改的话可以使用带修莫队 瞎搞。当然这道题$cost(i,j)$非常显然的满足四边形不等式。

所以这道题 cost(i,j)是很难求的问题但是我们可以利用其指针不断移动的优点来求 但是这里已经不再适合二分+单调队列优化决策了。

我们要采取一种新的方式优化决策.分治优化dp 当然是现学的 因为动态查询逆序对必须使用上述两种方法来搞这样复杂度过高 $n \cdot k \cdot logn \cdot \sqrt{n} \cdot logn$

没必要对不对我干嘛要写这么麻烦的东西 我们完全可以采取一些方法来进行优化这个过程。

具体是这样的逆序对虽然难求但是我们每次要求的f值是一整段区间并且我们知道如果是连续的区间取的话会非常的容易计算。

非常具体的我们采用分治的方法 solve(l,r,L,R)表示我们要求l到r这个区间当前的f值显然有 我们将其分治一半先对于mid找到一个最优的决策再分治这样L,R也是可以被分治的。

很巧妙 决策单调性套分治巧妙的结合在了一起,这显然是一个小trick.

分治logn层每层决策集合和求值集合在每一层总和为n树状数组每次移动logn 故总复杂度为 $n\cdot k \cdot log^2n$

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define EPS 1e-8
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=25010,maxn=30;
const int INF=0x3f3f3f3f;
int n,m;
int ql,qr,cnt;
int f[MAXN],a[MAXN];
int g[MAXN];
int c[MAXN];
inline void add(int x,int y)
{
    while(x<=n)
    {
        c[x]+=y;
        x+=x&(-x);
    }
}
inline int ask(int x)
{
    int ans=0;
    while(x)
    {
        ans+=c[x];
        x-=x&(-x);
    }
    return ans;
}
inline void addl(int x)
{
    add(a[x],1);
    cnt=cnt+(qr-ql-ask(a[x]-1));
}
inline void addr(int x)
{
    add(a[x],1);
    cnt+=ask(a[x]-1);
}
inline void dell(int x)
{
    add(a[x],-1);
    cnt=cnt-(qr-ql+1-ask(a[x]-1));
}
inline void delr(int x)
{
    add(a[x],-1);
    cnt-=ask(a[x]-1);
}
inline void calc(int l,int r)
{
    while(ql>l)addl(--ql);
    while(qr<r)addr(++qr);
    while(ql<l)dell(ql++);
    while(qr>r)delr(qr--);
}
inline void solve(int l,int r,int L,int R)//L~R转移决策区间
{
    int mid=(l+r)>>1;
    int p;//当前最优决策点
    for(int i=min(R,mid-1);i>=L;--i)
    {
        //if(g[i]==INF)continue;
        calc(i+1,mid);//计算mid
        if(g[i]+cnt<f[mid])f[mid]=g[i]+cnt,p=i;
    }
    if(l<mid)solve(l,mid-1,L,p);
    if(r>mid)solve(mid+1,r,p,R);
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;++i)a[i]=read();
    memset(f,0x3f,sizeof(f));
    memset(g,0x3f,sizeof(g));
    ql=1;qr=0;g[0]=0;//上一次的决策
    for(int i=1;i<=m;++i)//进行m次转移
    {
        solve(1,n,0,n-1);
        for(int i=1;i<=n;++i)g[i]=f[i]; 
    }
    printf("%d\n",f[n]);
    return 0;
}
View Code
LINK:假面舞会 感觉这道题挺神仙的说实话 画半天图也只想到了 环的gcd 链的最大值 体现出环的东西如何判定什么的...
关键是没想到好的做法 判断一个环的大小 我以前从来没有研究过  其实判断一个环的大小最主要的方法是dfs 当我第二次dfs到的时候深度之差就是环的大小。
关于这道题把握好三点 :
1.环的大小要判断准确 最大值显然为gcd 最小值为gcd最小的大于3的因子 这里判环 有两种形式都是可以体现出环的大小的 1直接的环2间接的环对于第第二种我们只能采取一种比较特殊的判环方法来搞。
即尽管是单向边我们仍然将其变成双向边 边权1 -1 区分开 因为这样有利于我们判断环的大小。
2.关于链的长短 我们采用双向边后 必须深度最大的减最小的+1这样判断链的长度正确性显然值得一提的是 最后判断不合法的时候 链长也要大于3
3.关于不合法的情况考虑清楚 n的大小 链长大小 环的大小 是否有自环等等... 
关键是单向边变成双向边的小trick 赋上边权判断环的大小.
//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define EPS 1e-8
#define ui unsigned int
#define pb push_back
#define RI register int
#define INF 1000000000
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=1000010,maxn=100010;
int n,m,len,flag,maxx,minn,sum,ans;//sum表示每一个联通分量中最长链之和
//ans表示图中环的gcd大小
int lin[maxn],ver[MAXN<<1],nex[MAXN<<1],e[MAXN<<1];
int vis[maxn],d[maxn];
inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
inline void add(int x,int y,int z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline void dfs(int x,int dep)
{
    vis[x]=1;d[x]=dep;
    maxx=max(maxx,dep);
    minn=min(minn,dep);
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(vis[tn])
        {
            int w=dep-d[tn]+e[i];
            if(w>0)ans=gcd(ans,w);
        }
        else dfs(tn,dep+e[i]);
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    if(n<3)flag=1;
    for(int i=1;i<=m;++i)
    {
        int x,y;
        x=read();y=read();
        if(x==y){flag=1;continue;}
        add(x,y,1);
        add(y,x,-1);
    }
    for(int i=1;i<=n;++i)
    {
        if(vis[i])continue;
        maxx=0;minn=INF;
        dfs(i,1);
        sum+=maxx-minn+1;
    }
    if(ans<3&&ans||flag||sum<3)
    {
        printf("-1 -1\n");
        return 0;
    }
    if(!ans)printf("%d %d\n",sum,3);
    else 
    {
        int w;
        for(int i=3;i<=ans;++i)if(ans%i==0){w=i;break;}
        printf("%d %d\n",ans,w);
    }
    return 0;
}
View Code

 LINK:毒瘤之神异之旅 这道题很久之前就有一半思路了 但是就是不知道怎么统计答案。

今天瞄了一眼题解知道怎么统计答案了...以前没想到直接暴力就好了。 求所有方案的答案之和 先考虑求出方案数 这显然不是隔板法 因为那样会引来重复。

需要dp 求方案数 考虑一下 $f[i][j]$表示大小为i分成j个数的方案数 显然有转移$f[i][j]+=f[i-1][j-1]+f[i-j][j]$即维护一个单调不升的序列。

这个转移的含义其实就是考虑一下最末尾的数字是否为1不为1的话我们想让整体都是一个单调不降的序列应该同时给整个序列+1 这样还是维护了原来的性质了也保证了当前这位>1至于是多少我并不关心。

也就是说 $f[i-j][j]$其实包含了当前第j为可以是的数的所有状态和方案数。当然有n^3的dp 这里不再赘述。

考虑一下 如何统计答案 我们暴力枚举最后的集合是什么样子的 显然对于所有的方案来说我们暴力枚举 是可以完整统计的 毕竟答案就是那个样子.

乘上方案数即可.但是值得一提的是需要容斥 枚举之后把和下一个地方相同的方案减掉即可。注意刚开始就要统计答案。滚动数组要滚动j。这题真有意思。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>    
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define EPS 1e-8
#define mod 1000000007
#define INF 1000000000
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const ll MAXN=10010;
ll n,k,m,u,cnt;
ll a[MAXN];
ll f[MAXN][2];
inline ll fast_pow(ll b,ll p)
{
    ll ans=1;
    while(p)
    {
        if(p&1)ans=ans*b%mod;
        p=p>>1;
        b=b*b%mod;
    }
    return ans;
}
signed main()
{
    //freopen("1.in","r",stdin);
    m=read();n=read();k=read();
    for(ll i=0;i<=m;++i)a[i]=fast_pow(i,k);
    u=1;f[0][u]=1;
    for(int k=1;k<=m;++k)if(m-n*k==0)cnt=(cnt+n*a[k]%mod)%mod;
    for(ll j=1;j<=n;++j)
    {
        u=u^1;
        for(ll i=0;i<=m;++i)
        {
            f[i][u]=0;
            if(i-1>=0)f[i][u]=(f[i][u]+f[i-1][u^1])%mod;
            if(i-j>=0)f[i][u]=(f[i][u]+f[i-j][u])%mod;
        }
        for(ll k=1;k<=m;++k)
        {
            ll w=n-j;
            if(m-w*k>=0)
            {
                int w1=0,w2=0;
                w1=f[m-w*k][u];
                if(m-(w+1)*k>=0)w2=f[m-(w+1)*k][u^1];
                cnt=(cnt+w*a[k]%mod*(w1-w2)%mod)%mod;
            }
            else break;
        }
    }
    printf("%lld\n",(cnt+mod)%mod);
    return 0;
}
View Code

 LINK:弦论 后缀数组+后缀自动机。

求一个字符串中第k小的子串。由于本人能力有限 后缀自动机不是太懂所以这里两种方法混合用。

首先考虑t=0的时候不同位置的相同子串算作一个 。考虑一个字符串中的所有后缀我们将其全部拉出来 那么对于一个字符串的所有子串来说就是以这么多后缀的第一个字符为开头到任意一个字符为结尾的所有字符串。

考虑如何其实对于第一问就是把这些东西全部都拉出来 然后去重 然后排序取出其中的第k个。不放假设我们可以先排序 再去重,再取出第k个当然也是等效的。

考虑后缀数组 排序之后对于前后两个串来说 我们要进行去重操作,其实就是两个串的LCP了 这么多都是重复的去掉即可。线性扫描即可得到答案。

考虑第二问不同位置的相同子串算作多个 这样就采取不了上述的方法了 因为我们的后缀排序排的只是后缀 对于子串的排序还是有局限性的。

比如说 之所以能够解决上述的问题 是因为排序之后利用LCP的性质解决的。仔细思考是正确的。

这一问 使得上述做法出现问题了。

再次咕了等我学会后缀自动机再说吧。

学过后缀自动机了 那么这个问题就很简单了 考虑第一问 本质不同的字符串 排序取第k个 我们知道DAG上的路径条数就是本质不同的字符串的个数 所以说我们直接求出f[i]表示从i出发的字符串的方案数即可 这个东西基数排序后反向拓扑即可.那么可以考虑第二问了 本质相同的也算 关于这个问题的答案其实是每个点的点权由1变成right集合的大小是挺显然的 我们知道到达某个点之后会和之前的形成若干个不同的字符串 每个字符串同时也有自己的数量其实这些字符串出现的次数就是right集合的大小了。求出两种不同的f[i]之后再DAG上跑就行了。

LINK:方程的解数 半天没反应过来 m^6次方或m^5次方过不了 考虑求出一半的答案然后将两部分整合在一起即可。排序+去重然后双指针扫描即可。

思路挺棒的双向搜这个东西还是挺重要的要记住哇。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>    
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define EPS 1e-12
#define mod 1000000007
#define INF 10000000000000000ll
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=10,maxn=150*150*150+10;
int n,m;
int a[MAXN],b[MAXN],v[maxn];
int q[maxn],s[maxn],c[maxn],top,top1,top2;
ll ans;
inline int ksm(int b,int p)
{
    int cnt=1;
    while(p)
    {
        if(p&1)cnt=cnt*b;
        p=p>>1;b=b*b;
    }
    return cnt;
}
inline void dfs(int x,int depth,int v)
{
    if(x==depth+1)
    {
        q[++top]=v;
        return;
    }
    for(int i=1;i<=m;++i)dfs(x+1,depth,v+a[x]*ksm(i,b[x]));
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;++i)a[i]=read(),b[i]=read();
    if(n==1)
    {
        for(int i=1;i<=m;++i)
            if(ksm(i,b[1])*a[1]==0)++ans;
        printf("%lld\n",ans);
        return 0;
    }
    int mid=n/2;
    dfs(1,mid,0);
    sort(q+1,q+1+top);
    for(int i=1;i<=top;++i)
        if(i==1||q[i]!=q[i-1])
            s[++top1]=q[i],++c[top1];
        else ++c[top1];
    top=0;
    dfs(mid+1,n,0);
    sort(q+1,q+1+top);
    for(int i=1;i<=top;++i)
        if(i==1||q[i]!=q[i-1])
            q[++top2]=q[i],++v[top2];
        else ++v[top2];
    for(int i=1,j=top2;i<=top1;++i)
    {
        while(s[i]+q[j]>0&&j>=1)--j;
        if(s[i]+q[j]==0)ans+=(ll)c[i]*v[j];
        if(!j)break;
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

 LINK:SDOI外星千足虫 感觉很不可做 感觉要递归的样子 也想到了几个等式加减 发现是或 但是没卵用...

感觉要递归处理觉得发现非常难写 而且也不可做 最后还是被源神一语道破天机,高斯消元 我真的没想到高斯消元...下次再遇到类似于等式的东西一定要想到高斯消元Y.

那么解法很显然了 我们进一步的发现 每一个等式其实是一个异或方程组然后解这个方程组即可。好久没写这个东西了怎么搞来着?跟平常的高消是一样的$n^2\cdot m$会T怎么办?

考虑一下优化这个过程01串直接开bitset再除以64,需要二分么?显然不必要因为我们边高消边判断未知数的情况即可。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>    
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define put(x) printf("%d\n",x)
#define EPS 1e-8
#define mod 1000000007
#define INF 10000000000000000ll
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
//异或高斯消元 我是个zz
const int MAXN=2010,maxn=1010;
int n,m,maxx,w,flag;
char a[maxn];
bitset<maxn>b[MAXN];
inline void GAUSS()
{
    for(int i=1;i<=n;++i)//消掉n个主元即可.
    {
        int p=i;maxx=max(maxx,i);
        for(int j=i;j<=m;++j)
            if(b[j][i]>0){p=j;break;}
        if(p!=i)
        {
            swap(b[i],b[p]);
            maxx=max(maxx,p);
        }
        if(!b[i][i]){flag=1;return;}
        for(int j=1;j<=m;++j)
        {
            if(i==j)continue;
            if(b[j][i])b[j]=b[j]^b[i];
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        scanf("%s",a+1);
        for(int j=1;j<=n;++j)
            if(a[j]=='1')b[i][j]=1;
        int x=read();
        b[i][n+1]=x;
    }
    GAUSS();
    if(flag)puts("Cannot Determine");
    else 
    {
        printf("%d\n",maxx);
        for(int i=1;i<=n;++i)if(!b[i][n+1])printf("Earth\n");
        else printf("?y7M#\n");
    }
    return 0;
}
View Code

 LINK:按钮BSGS 这道题挺唤醒了我很多数论的回忆 以为是一个扩展BSGS最后发现K , M 一定互质。这里有一个显然的结论是 (K,M)!=1时一定无解我也不知道为什么显然了?????

首先题目意思是让我们求 $K^x\equiv 1 (mod M)$ 求出这个东西 显然这个是一个高次同余方程 但是BSGS算法基于简化剩余系 当且仅当 (K,M)==1 有简化剩余系。

但是欧拉定理 $a^{\phi (p)}\equiv 1(mod p)$这个等式成立的充要条件是 (a,p)==1 也就是上述等式成立且x为正整数显然 K M需要满足互质的情况。此时x为 phi(M)但是存在一个小问题x可能不是最小的 设x为最小的整数的话那么我们显然可以得到是phi(M)的因子这个可以反证法我就不再赘述具体的 kx<phi(M)<(k+1)x 接下来都带到等式之中即可。

也显然我们可以直接利用BSGS求解。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>    
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define ld long double
#define put(x) printf("%d\n",x)
#define EPS 1e-8
#define mod 998244353
#define INF 10000000000000000ll
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=1000003;
int a,b,flag;//求解a^x=1(mod b)
inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
map<ll,int>H;
inline int BSGS()
{
    int S=(int)sqrt(b*1.0)+1;
    ll w=1;
    for(int i=1;i<S;++i)
    {
        w=w*a%b;
        H[w]=i;
    }
    //w为a^S次方
    w=w*a%b;
    ll c=w;
    for(int i=1;i<=S;++i)
    {
        if(c==1)return i*S;
        if(H.find(c)!=H.end())return i*S-H[c];
        c=c*w%b;
    }
    return 0;
}
int main()
{
    //freopen("1.in","r",stdin);
    b=read();a=read();
    if(gcd(a,b)!=1)puts("Let's go Blue Jays!");
    else
    {
        flag=BSGS();
        if(flag)printf("%d\n",flag);
    }
    return 0;
}
View Code

LINK:TJOI2008 公共子串 这道题目算是很经典的模型了 求三个字符串的最长子串 可是其子串没有规定好 我起初也不知道是不连续的子串  不连续的子串求公共子串的方案数 而且需要子串是本质不同的 那么这道题就不能使用SAM来求解了 需要dp 。

我也想了半天 先从简单的问题出发求两个字符串的公共子串的方案数?怎么搞 显然的是设f[i][j]表示第一个匹配到了i这个位置第二个字符串匹配到了第j个位置的方案数.

我们很显然得到一个状态转移 if(a[i]==b[j])f[i][j]+=f[i-1][j-1]; 除了这个呢?我们陷入深思 f[i][j]+=f[i-1][j]; 还是f[i][j]+=f[i][j-1]两个都写显然是不对的 都写可能会需要容斥?不妨从这个转移的本质来看 我们转移时 表示的是 i j 截止所形成的方案数 f[i][j]+=f[i-1][j-1]这个状态转移表示的是我们在上一步的基础之上增加了一个字符 还有呢如果我当前不增加字符会是什么样子 f[i-1][j] 表示当前我不和j进行匹配的方案数 f[i][j-1]也是这个样子 我们发现f[i-1][j]不等于f[i][j-1]所以我认为两个状态应该一起加上 所带来的重复呢?我觉得是f[i-1][j-1]; 这个也很显然..于是状态转移就有了..

考虑本质不同 想一下什么时候回产生相同的字符串 i和j产生了一个字符a i又可以和j'产生一个字符a 这两个字符串必然有一个是要被舍弃掉的为了使答案合法我们应该舍弃掉后者 于是乎我们只需要限定i只和一个最早的j产生匹配即可.

考虑三个 同理以上.发现我推错了 这题真的很鬼Y ...

看完题解是这样写的 不考虑它在到底长的是什么样子 而是考虑 其长的最后一个字符会是什么 这两个是不等价的 但是在方案数上是等价的 暴力枚举最后其长啥样子然后靠last数组转移即可 还好题目中要求了本质不同 可以使用last转移不然 复杂度还会上升 这也是两种状态转移的区别.

但是这个dp很新颖 枚举最后一个是啥东西转移 而并非考虑其长啥样子 同时我也承认 上面那个容斥dp我不确定是否正确...但是这道题确实精辟 值得思索.

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<vector>
#include<stack>
#include<deque>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<bitset>
#include<map>
#include<set>
#define ll long long
#define len(i) t[i].len
#define f(i) t[i].fa
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=110;
ll f[MAXN][MAXN][MAXN];
int las[4][MAXN<<1];
char a[MAXN],b[MAXN],c[MAXN];
int A,B,C;
int main()
{
    //freopen("1.in","r",stdin);
    scanf("%s",a+1);A=strlen(a+1);
    scanf("%s",b+1);B=strlen(b+1);
    scanf("%s",c+1);C=strlen(c+1);
    for(int i=1;i<=A;++i)
    {
        memset(las[2],0,sizeof(las[2]));
        las[1][(int)a[i]]=i;
        for(int j=1;j<=B;++j)
        {
            memset(las[3],0,sizeof(las[3]));
            las[2][(int)b[j]]=j;
            for(int k=1;k<=C;++k)
            {
                las[3][(int)c[k]]=k;
                for(int w='a';w<='z';++w)
                {
                    int w1=las[1][w];
                    int w2=las[2][w];
                    int w3=las[3][w];
                    if(w1&&w2&&w3)f[i][j][k]+=f[w1-1][w2-1][w3-1]+1;
                }
            }
        }
    }
    printf("%lld\n",f[A][B][C]);
    return 0;
}
View Code

 LINK:奇特の排序... 具体意思是 一堆物品 体积是一个排列 还有一个属性重量可以重复 每次可以将两个物品交换位置所花费的代价是两个物品的质量之和 问将所有物品的体积 从小到大排序需要花费的最小代价是多少?这是一个很有意思的问题 考虑一下其实较重的物品我们期望其最多只动一次 我们考虑 分循环考虑问题 因为分属两个不同的循环我们没必要交换 形成一个更大的循环除去一种特殊的情况就是全局最小值来帮忙 除掉这种情况之外的 变成更大循环的情况都是不优的,显然。分循环考虑后我们发现每一个循环我们交换其内部的东西需要的最小次数为sz-1 考虑到让最小值来辅助 那就是最小值移动了n-1次 其他的物品移动了1次 符合最优的情况,但也可能是 找一个循环之外的值来帮忙 其实就是全局最小值了,显然 然后全局最小值交换了sz+1次 局部最小值交换了2次 其余的值交换了1次,可以证明这两种策略是最优的。

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<utility>
#include<cctype>
#include<string>
#include<bitset>
#include<vector>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<list>
#include<set>
#include<map>
#define v(i) t[i].v
#define w(i) t[i].w
#define INF 1000000000
#define ll long long
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=200010;
int n;
struct wy
{
    ll v,w;
}t[MAXN];
ll ans,minn=INF;
int vis[MAXN];
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;++i)v(i)=read();
    for(int i=1;i<=n;++i)w(i)=read(),minn=min(minn,w(i));
    for(int i=1;i<=n;++i)
    {
        if(!vis[i])
        {
            ll x=i,cnt=0,ww=INF,sz=0;
            while(!vis[x])
            {
                cnt+=w(x);
                vis[x]=1;
                ++sz;
                ww=min(ww,w(x));
                x=v(x);
            }
            ans+=min(ww*(sz-2)+cnt,minn*(sz+1)+cnt+ww);
        }
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

 LINK:POI 2015 MYJ 洗车 此题可谓是神题,我发现这题 很鬼 很难想。 题目的大致意思是:有一些顾客 每个人都会在各自的 l 和 r内找到一家最便宜的洗车店洗车但是如果这个洗车店的费用大于自己所有的钱ci 那么就不花费 反之则花费这个洗车店的费用,构造一个序列 使得总花费最大。

很让人摸不着头脑 很难凭空构造出这样的一个序列,我们可以尝试挖掘一些性质,显然最优解中必然有一种使得所有位置的值 si属于c{}这个集合,挺显然的。所以我们就可以先将所有费用ci离散化了 考虑我们先由较小区间开始构造再扩展到大区间 设状态 f[i][j][k]表示i~j这个区间内最小值为k的最大收益 另外的 对于转移我们强制性悬着一个定点 w使得w的值为 k 那么显然有 f[i][j][k]=max(f[i][j][k],f[i][w-1][k]+f[w+1][j][k]+cost) 观察这个状态转移方程 这符合我们状态的定义 但是有点小问题的是 我们的决策f[i][w-1][k]和f[w+1][j][k]这个其实不一定刚好是k >=k也同时符合我们的状态的定义所以我们状态有了拓展 f[i][j][k]表示i~j这个区间内最小值>=k的最大花费。再记录一下决策就解决了这道题 说实话很鬼畜的 区间dp...

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<utility>
#include<cctype>
#include<string>
#include<bitset>
#include<vector>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<list>
#include<set>
#include<map>
#define INF 1000000000
#define ll long long
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=52,maxn=4010;
int n,m,cnt;
struct wy
{
    int x,y,z;
}t[maxn];
int a[MAXN];
int b[maxn],sum[maxn];
int f[MAXN][MAXN][maxn],c[MAXN][MAXN][maxn],v[MAXN][MAXN][maxn];
inline void discrete()
{
    sort(b+1,b+1+m);
    for(int i=1;i<=m;++i)if(i==1||b[i]!=b[i-1])b[++cnt]=b[i];
    for(int i=1;i<=m;++i)t[i].z=lower_bound(b+1,b+1+cnt,t[i].z)-b;
}
inline void get(int x,int y,int z)
{
    if(x>y)return;
    a[c[x][y][z]]=v[x][y][z];
    get(x,c[x][y][z]-1,v[x][y][z]);
    get(c[x][y][z]+1,y,v[x][y][z]);
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int x,y,z;
        x=read();y=read();z=read();
        b[i]=z;t[i]=(wy){x,y,z};
    }
    discrete();
    for(int len=1;len<=n;++len)
    {
        for(int i=1;i<=n-len+1;++i)
        {
            int j=i+len-1;
            for(int w=i;w<=j;++w)
            {
                for(int k=1;k<=cnt;++k)sum[k]=0;
                for(int k=1;k<=m;++k)
                {
                    if(t[k].x>=i&&t[k].y<=j)
                        if(w>=t[k].x&&w<=t[k].y)++sum[t[k].z];
                }
                for(int k=cnt;k>=1;--k)sum[k]+=sum[k+1];
                for(int k=1;k<=cnt;++k)
                {
                    if(f[i][w-1][k]+f[w+1][j][k]+sum[k]*b[k]>=f[i][j][k])
                    {
                        f[i][j][k]=f[i][w-1][k]+f[w+1][j][k]+sum[k]*b[k];
                        c[i][j][k]=w;v[i][j][k]=k;
                    }
                }
            }    
            for(int k=cnt-1;k>=1;--k)
                if(f[i][j][k+1]>f[i][j][k])
                {
                    f[i][j][k]=f[i][j][k+1];
                    c[i][j][k]=c[i][j][k+1];
                    v[i][j][k]=v[i][j][k+1];
                }
        }
    }
    printf("%d\n",f[1][n][1]);
    get(1,n,1);
    for(int i=1;i<=n;++i)printf("%d ",b[a[i]]);
    return 0;
}
View Code

 LINK:AFOI-19面基 有意思 一棵树 每次有一个点爆炸 影响的范围为k 每条边权值为1 影响到的范围内的点也会GG 和点相连的边也会GG.

一条边GG定义的权值是这条边两端的点数之积 问 哪个点爆炸所带来的权值最大?

不难想到预处理出所有的边 也不难想到换根 关键怎么统计答案,n^2暴力看起来很好写.

换根之后怎么统计答案是关键 处理不好我们这个换根相当于白换 首先一个比较显然的操作是把边的权值挂到点上 随手画图 之后发现k为偶数我们统计距当前爆炸点为偶数距离的权值和 反之则统计奇数距离的权值和 这个容斥的过程还是很显然的 所以我们就有一个nk的换根了..(实现起来还有点小难写..

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<utility>
#include<cctype>
#include<string>
#include<bitset>
#include<vector>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<list>
#include<set>
#include<map>
#define INF 2147483647
#define ll long long
#define pb push_back
#define M 998244353
using namespace std;
char *fs,*ft,buf[1<<15];
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=30010;
int n,k,len;
int w[MAXN];
ll s[MAXN],ans;
ll f[MAXN][210],g[MAXN][210];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1];
inline void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
inline void dfs(int x,int father)
{
    w[x]=1;
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(tn==father)continue;
        dfs(tn,x);
        w[x]+=w[tn];
        s[x]+=w[tn]*(n-w[tn]);
        s[tn]+=w[tn]*(n-w[tn]);
    }
}
inline void calc(int x,int father)
{
    f[x][0]=s[x];
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(tn==father)continue;
        calc(tn,x);
        for(int j=1;j<=k;++j)f[x][j]+=f[tn][j-1];
    }
}
inline void dp(int x,int father)
{
    ll cnt=0;
    if(k&1)for(int i=1;i<=k;i=i+2)cnt+=g[x][i];
    else for(int i=0;i<=k;i=i+2)cnt+=g[x][i];
    ans=max(ans,cnt);
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(tn==father)continue;
        for(int j=1;j<=k;++j)g[x][j]-=f[tn][j-1];
        g[tn][0]=f[tn][0];
        for(int j=1;j<=k;++j)g[tn][j]=f[tn][j]+g[x][j-1];
        for(int j=1;j<=k;++j)g[x][j]+=f[tn][j-1];
        dp(tn,x);
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();k=read();
    for(int i=1;i<n;++i)
    {
        int x,y;
        x=read();y=read();
        add(x,y);add(y,x);
    }
    dfs(1,0);
    //for(int i=1;i<=n;++i)cout<<s[i]<<endl;
    calc(1,0);
    for(int i=0;i<=k;++i)g[1][i]=f[1][i];
    dp(1,0);
    printf("%lld\n",ans);
    return 0;
}
View Code

 LINK:SDOI2008校门外的区间 这道题其实是想让我们完成一些区间操作,一些操作我们仔细分析都是可以分析出来的。

唯一不好处理的是这四种区间 [,] [,) (,] (,) 我写了4种标记发现非常难写,最终还是看了题解 题解中有一个很妙的处理就是说把区间中所有数字当成两个,那么开闭区间就可以直接映射在某个位置了 可谓是非常的巧妙,值得借鉴。

自闭了 咕了。

LINK:无向图三元环计数 这道题是一扶苏一上传的 话说为什么要叫yifusuyi 为什么不叫扶苏呢?真奇怪...

无向图 求本质不同的三元环 就是就是一个简单环 由三个点组成。 显然=有n^3 nm m^2等等都过不了的算法。

正解是这样的 考虑一下我们把图转换一下 转成有向图 规定 度数小的连向度数大的 度数一样编号小的向编号大的连。

这样图中一定无环 可以考虑一下有环是什么情况的显然不可能。

我们发现这种图上 u->w v->w 这三个点和原图中的三元环是等价的。暴力枚举u 标记所有的出点 那么我们只要再找到v也能访问到那些出点就是这些三元组的数量了。

考虑一下复杂度 n*out[u] 由于一部分点<=sqrt(m) 那么这样复杂度为n *sqrt(m) +m 还有$\sum{out[v]}$的复杂度呢,还是考虑 out[v]中有sqrt(m)度数 那么这样做事 m*sqrt(m) 还有一部分大于sqrt(m) 这样的点不超过sqrt(m)个 而其只能向>sqrt(m)度数的点连边所以出边最多sqrt(m)个所以总复杂度msqrt(m) 非常神奇对不对...

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>  
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 1000000000
#define ll long long
#define max(x,y) (x>y?x:y)
#define min(x,y) (x>y?y:x) 
#define db double
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=200010;
int n,m,len,du[MAXN],w[MAXN];
ll ans;
int x[MAXN],y[MAXN];
int lin[MAXN],ver[MAXN],nex[MAXN];
inline void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        x[i]=read();y[i]=read();
        ++du[x[i]];++du[y[i]];
    }
    for(int i=1;i<=m;++i)
    {
        if(du[x[i]]>du[y[i]])swap(x[i],y[i]);
        if(du[x[i]]==du[y[i]])if(x[i]>y[i])swap(x[i],y[i]);
        add(x[i],y[i]);
    }
    for(int i=1;i<=n;++i)
    {
        for(int j=lin[i];j;j=nex[j])w[ver[j]]=i;
        for(int j=lin[i];j;j=nex[j])
        {
            int tn=ver[j];
            for(int k=lin[tn];k;k=nex[k])if(w[ver[k]]==i)++ans;
        }
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

 LINK:USACO12NOV Balanced Trees

给定一颗树 每个节点都有一个括号 问所有括号平衡的路径中 深度最大为多少。

这里的深度最大 是指 ()()() 深度为1 ((())) 深度为3. 是这个意思 $n\leq 40000$

首先 答案一定是一个 括号平衡的 路径,答案的特征 深度最大 我们可以理解为 最多有多少个括号再嵌套 进一步的对于一个合法的括号序列 算出其答案 如果有一个左括号那么++cnt 遇到一个右括号说明少了一个括号进行嵌套--cnt cnt的最大值即这个答案的代价.

考虑怎么做?显然我们设f[i][j]表示i到j之间的最大值 我们再结合几个奇奇怪怪的数组不难推出答案 但是过不了 对于树上路径问题我们首选点分治。

这道题的点分治就很有意思了我们可以发现答案 可以变成 过分治中心和不过分治中心,分治即可。

考虑合并答案 以我们点分中心进行合并 首先对于若干链 我们需要保证其在一定程度上是合法的再考虑合并 合并分为两种 正着和倒着的进行合并。

合并时 我们采用上述方法合并即可.  咕掉。(看我能咕多少题...

LINK:春节十二响 作为去年省选我唯一拿分的题目 这道题显得尤为感动人心。

首先 名字好评 和题目描述也挺相近 而且也很感动啊 毕竟day1自闭了。好吧其实day2 更鬼畜。。。

这道题是给出了一棵树让我们给树上的点分组 每一组的代价为 这个组之中的点权最大值且组内满足任意两个不是祖先关系 总体代价为各组代价值和 问经过一些操作给点分组后使得总体点权和最小。

如何分组显得尤为重要 我们先分析链怎么搞 祖先关系很明确 我们给左右两边排序然后两两匹配即可。这个贪心看起来很对。

如何把这个贪心扩展到树上。问题关键源头是当前的最大值 这个最大值不管分到了哪一组 代价都是其点权 我们既然知道了必须要付出这个点权为什么不让其带走其他的点,我们考虑最大的带走次大的这样 对后面的影响不会更差,我们是可以证明这一点的。

所以我们考虑用最大值带走其他的点 我们将点权从小到大排序然后进行贪心即可. 这样可以获得60分 加上链就有75分了 真良心。

 //#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 1000000000
#define ll long long
#define a(i) s[i].a
#define x(i) s[i].x
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=2010;
int n,id,flag,t,len;
ll ans;
int d[MAXN],vis[MAXN],dfn[MAXN],las[MAXN];
int lin[MAXN],ver[MAXN],nex[MAXN],q[MAXN];
struct wy
{
    int a,x;
    inline int friend operator <(wy a,wy b)
    {
        return a.a>b.a;
    }
}s[MAXN];
inline void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
inline void dfs(int x,int father)
{
    d[x]=d[father]+1;dfn[x]=++id;
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(tn==father)continue;
        dfs(tn,x);
    }
    las[x]=id;
}
inline int check(int x,int y)
{
    if(d[x]<d[y])swap(x,y);
    if(dfn[y]<=dfn[x]&&las[y]>=dfn[x])return 0;
    return 1;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    if(n<=2000)
    {
        for(int i=1;i<=n;++i)a(i)=read(),x(i)=i;
        for(int i=2;i<=n;++i)
        {
            int x=read();
            add(x,i);
        }
        dfs(1,0);flag=1;
        vis[1]=1;ans+=a(1);
        sort(s+1,s+1+n);
        while(flag<n)
        {
            t=0;
            for(int i=1;i<=n;++i)
            {
                if(vis[x(i)])continue;
                int cnt=0;
                for(int j=1;j<=t;++j)if(!check(x(i),q[j])){cnt=1;break;}
                if(!cnt)
                {
                    if(!t)ans+=a(i);
                    q[++t]=x(i),vis[x(i)]=1;
                }
            }
            flag+=t;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
View Code

什么?关于贪心的证明?我个人觉得可以从单点加入集合的时候证明 毕竟我是这样证的 读者自证不难啦...

考虑100分怎么做 n是200000 如何优化上述做法 我们发现 上述做法每次都在找点 融进集合中 我们难免每次都对于一个点去找集合合并 为什么我们要寻找不能自然的合并么?这可是棵树,我们考虑在树上进行这个合并操作首先是基于以某个点为根的合并 然后我们进行子树之间的合并 按照大小的关系不就不需要判断能否融入某个集合了么。于是解法就有了我们在树上进行合并 递归进行 合并 若干个儿子的时候 贪心即可.可以证明这个过程和上述过程是等效的。

最后再简述两个关键点 :1 如何想到这个方法 我用到的方法还是正难则反 我从暴力中观察出来我们正着去找点时间复杂度难免很大,我们想着倒着来 就是说让每个点进行自然的合并而不是我们刻意的去合并 这个时候 想要自然的合并还是有集合的限制的 所以我们考虑到了在树上进行这个过程就可以免去判断进行自然的合并。

2 我们发现 合并完集合后集合放哪是一个问题我们不可能把一个集合放到x这个节点处 这样移动 是O(n)的再者 我们存集合只存大小 但是这是不可以归并的 因为一归并就n^2了 所以我们考虑放到堆或set中(近期发行set常数比堆大 所以放堆里 然后我们发现集合是不能移动的 所以我们考虑一直的放到某个点的处 w[x]表示我们那个集合指向的位置即可.

还不算很简单啊 有点思维高度.

 //#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 1000000000
#define ll long long
#define a(i) s[i].a
#define x(i) s[i].x
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=200010;
int n,id,flag,t,len;
ll ans;
int d[MAXN],vis[MAXN],dfn[MAXN],las[MAXN];
int lin[MAXN],ver[MAXN],nex[MAXN],q[MAXN],w[MAXN],sz[MAXN];
priority_queue<int>b[MAXN];
struct wy
{
    int a,x;
    inline int friend operator <(wy a,wy b)
    {
        return a.a>b.a;
    }
}s[MAXN];
inline void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
inline void dfs(int x,int father)
{
    d[x]=d[father]+1;dfn[x]=++id;
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(tn==father)continue;
        dfs(tn,x);
    }
    las[x]=id;
}
inline int check(int x,int y)
{
    if(d[x]<d[y])swap(x,y);
    if(dfn[y]<=dfn[x]&&las[y]>=dfn[x])return 0;
    return 1;
}
inline void dp(int x)
{
    int mark=0;
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        dp(tn);
        t=0;
        if(!mark)mark=1,w[x]=w[tn];
        else
        {
            if(sz[w[x]]>sz[w[tn]])
            {
                while(b[w[tn]].size())
                {
                    int w1=b[w[tn]].top();
                    int w2=b[w[x]].top();
                    b[w[tn]].pop();
                    b[w[x]].pop();
                    q[++t]=max(w1,w2);
                }
                for(int j=1;j<=t;++j)b[w[x]].push(q[j]);
            }
            else
            {
                while(b[w[x]].size())
                {
                    int w1=b[w[tn]].top();
                    int w2=b[w[x]].top();
                    b[w[tn]].pop();
                    b[w[x]].pop();
                    q[++t]=max(w1,w2);
                }
                for(int j=1;j<=t;++j)b[w[tn]].push(q[j]);
                w[x]=w[tn];
            }
        }
    }
    if(!mark)b[x].push(a(x)),w[x]=x,sz[x]=1;
    else {b[w[x]].push(a(x)),++sz[w[x]];}
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;++i)a(i)=read(),x(i)=i;
    for(int i=2;i<=n;++i)
    {
        int x=read();
        add(x,i);
    }
    if(n<=2000)
    {
        dfs(1,0);
        vis[1]=1;ans+=a(1);
        sort(s+1,s+1+n);flag=1;
        while(flag<n)
        {
            t=0;
            for(int i=1;i<=n;++i)
            {
                if(vis[x(i)])continue;
                int cnt=0;
                for(int j=1;j<=t;++j)if(!check(x(i),q[j])){cnt=1;break;}
                if(!cnt)
                {
                    if(!t)ans+=a(i);
                    q[++t]=x(i),vis[x(i)]=1;
                }
            }
            flag+=t;
        }
        printf("%lld\n",ans);
    }
    else
    {
        dp(1);
        while(b[w[1]].size())
        {
            ans+=b[w[1]].top();
            //cout<<b[w[1]].top()<<endl;
            b[w[1]].pop();
        }
        printf("%lld\n",ans);
    }
    return 0;
}
View Code

 LINK:CQOI2015 选数 这道题的数论形式和其他题目都不太相近...还是观察怎么解决问题吧..

想完后 和其他基本的题目一毛一样 先把题目中要求的 用式子写出来 就是一堆sigma的那种 然后 瞎推一下 发现 就可以整除分块了 然后 杜教筛一波即可.

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>  
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 1000000000000000000ll
#define ll long long
#define max(x,y) (x>y?x:y)
#define min(x,y) (x>y?y:x) 
#define db double
#define EPS 1e-6
#define mod 1000000007
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    register ll x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const ll MAXN=1000010;
ll N,K,L,H,top;
ll p[MAXN],mu[MAXN],v[MAXN];
ll maxx=1000000;
map<ll,ll>s;
ll ans;
inline int fast_pow(ll b,ll p)
{
    ll cnt=1;
    while(p)
    {
        if(p&1)cnt=cnt*b%mod;
        p=p>>1;
        b=b*b%mod;
    }
    return cnt;
}
inline void prepare()
{
    mu[1]=1;
    for(ll i=2;i<=maxx;++i)
    {
        if(!v[i])
        {
            v[i]=i;
            p[++top]=i;
            mu[i]=-1;
        }
        for(ll j=1;j<=top;++j)
        {
            if(maxx/i<p[j])break;
            ll ww=i*p[j];
            v[ww]=p[j];
            if(v[i]==p[j])break;
            mu[ww]=-mu[i];
        }
    }
    for(ll i=1;i<=maxx;++i)mu[i]+=mu[i-1];
}
inline ll djs(ll x)
{
    if(x<=maxx)return mu[x];
    if(s.find(x)!=s.end())return s[x];
    ll ans=1,ww,w;
    for(ll i=2;i<=x;i=ww+1)
    {
        w=x/i;ww=x/w;
        ans=ans-(ww-i+1)*djs(w);
    }
    return s[x]=ans;
}
int main()
{
    //freopen("1.in","r",stdin);
    N=read();K=read();L=read();H=read();
    L=(L-1)/K+1;H=H/K;
    maxx=min(maxx,H);
    prepare();
    ll w1,w2,ww;
    for(ll i=1;i<=L-1;i=ww+1)
    {
        w1=(L-1)/i;w2=H/i;
        ww=min((L-1)/w1,H/w2);
        ans=(ans+(djs(ww)-djs(i-1))*fast_pow(w2-w1,N)%mod)%mod;
    }
    for(ll i=L;i<=H;i=ww+1)
    {
        w1=H/i;ww=H/w1;
        ans=(ans+(djs(ww)-djs(i-1))*fast_pow(w1,N)%mod)%mod;
    }
    printf("%lld\n",(ans+mod)%mod);
    return 0;
}
View Code

 LINK:SCOI2016 萌萌哒 遇到好题我就想记录 下来 由其是这种对于算法的应用的题目。

$nm$+并查集的算法大家显然都会 考虑一下正解 我们 发现询问是区间对应区间我们不能直接暴力的去处理区间。

所以显然的我们可以上先单数进行类似的等效连边 由于所有的点都是这种等效连边所以是可行的 我们只是把一个区间对应到另一个区间上了 需要考虑一些问题 虽然把区间分成了logn个区间了 但是如果某个小区间同时被两个区间都给标记了 如何合并?不妨此时我们利用并查集把区间合起来都连到最靠左的那个地方。

这样我们就做完了区间 考虑一下如何查询答案 我们发现再怎么覆盖也终究只有n个区间我们把 由于我们刚才进行的是等效连边所以看起来非常的不爽 需要下方 所以进行下放即可.最后数一下个数即可.超级有趣的题目 值得一提的是这个地方线段树没有任何作用我们简单的利用ST表 或者 类似于倍增的东西搞一下就行了 分成logn个区间。

 //#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 1000000000
#define ll long long
#define mod 1000000007
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    register int x=0,f=1;register char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=100010;
int n,m,id;
ll ans;
int f[MAXN][20],pos[MAXN*20];
int Log[MAXN],fa[MAXN*20];
inline int getfather(int x){return x==fa[x]?x:fa[x]=getfather(fa[x]);}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=2;i<=n;++i)Log[i]=Log[i>>1]+1;
    for(int j=0;j<=Log[n];++j)
        for(int i=1;i<=n-(1<<j)+1;++i)
        {
            f[i][j]=++id;
            fa[id]=id;
            pos[id]=i;
        }
    for(int i=1;i<=m;++i)
    {
        int l,r,L,R;
        l=read();r=read();
        L=read();R=read();
        if(l==L)continue;
        int w=r-l+1;
        int st1=l,st2=L;
        for(int j=Log[w];j>=0;--j)
        {
            if(w&(1<<j))
            {
                int w1=getfather(f[st1][j]);
                int w2=getfather(f[st2][j]);
                if(w1==w2)continue;
                if(pos[w1]<pos[w2])fa[w2]=w1;
                else fa[w1]=w2;
                st1+=(1<<j);st2+=(1<<j);
            }
        }
    }
    for(int j=Log[n];j>=1;--j)
    {
        for(int i=1;i<=n-(1<<j)+1;++i)
        {
            int w1=getfather(f[i][j]);
            if(w1==f[i][j])continue;
            int w2=getfather(f[i][j-1]);
            int w3=getfather(f[pos[w1]][j-1]);
            if(pos[w2]<pos[w3])fa[w3]=w2;
            else fa[w2]=w3;
            w2=getfather(f[i+(1<<(j-1))][j-1]);
            w3=getfather(f[pos[w1]+(1<<(j-1))][j-1]);
            if(pos[w2]<pos[w3])fa[w3]=w2;
            else fa[w2]=w3;
        }
        //cout<<j<<endl;
    }
    ans=9;
    for(int i=2;i<=n;++i)
    {
        int xx=getfather(f[i][0]);
        if(xx==f[i][0])ans=ans*10%mod;
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

 LINK:红色的幻想乡 这道题 我真真真的不是很会 瞄了几眼题解才知道怎么做。

题目本身很有意思 一个n*m的矩阵 初始都是空的 然后可以随便再一个位置上放炸弹 使得整行整列 除了当前这个位置都充满红雾。

一个位置被影响到两次 红雾的效果就会被抵消掉,求每次操作后 有多少个位置有红雾出现。

每次都是单调修改 但是n m的范围是1e5 线段树套线段树是没用的 复杂度过高 而且是不具有区间可加性的修改。

好像很难求答案 比如说我们现在维护了一个cnt 表示空地个数 经过修改后我们很难在log 或 log^2的时间内把cnt 给更新掉。

所以得考虑另一种统计答案的方式 我们知道一共有nm个位置 有x行被红雾点到 y列被红雾点到 通过这个信息能否求答案呢?

显然是可以的 我们不难发现答案为 x*n-y*m+x*y*2; 这是很显然的 不过 细节是x行被点到的意思是整行只出现奇数个红雾点。

我们维护这个东西即可.很妙啊。容斥。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>  
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 1000000000000000000ll
#define ll long long
#define max(x,y) (x>y?x:y)
#define min(x,y) (x>y?y:x) 
#define db double
#define EPS 1e-6
#define mod 998244353
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    register int x=0,f=1;register char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=100010;
int n,m,Q;
int c[MAXN],b[MAXN];
int r[MAXN],l[MAXN];
inline void add(int x,int y){while(x<=n){c[x]+=y;x+=x&(-x);}}
inline void add1(int x,int y){while(x<=m){b[x]+=y;x+=x&(-x);}}
inline int ask(int x){int cnt=0;while(x){cnt+=c[x];x-=x&(-x);}return cnt;}
inline int ask1(int x){int cnt=0;while(x){cnt+=b[x];x-=x&(-x);}return cnt;}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();Q=read();
    for(int i=1;i<=Q;++i)
    {
        int op;
        int x,y,xx,yy;
        op=read();x=read();y=read();
        if(op==1)
        {
            add(x,r[x]?-1:1);
            r[x]^=1;
            add1(y,l[y]?-1:1);
            l[y]^=1;
        }
        else
        {
            xx=read();yy=read();
            ll w1=ask(xx)-ask(x-1);
            ll w2=ask1(yy)-ask1(y-1);
            ll H=xx-x+1;ll L=yy-y+1;
            printf("%lld\n",w1*L+w2*H-w1*w2*2);
        }
    }
    return 0;
}
View Code

压行感觉很爽...

LINK:差分约束 做多项式的题做自闭了 开始新的支线 图论 先来几道差分约束...

大体上就是建图 跑约束条件 注意判负环 被卡的时候尝试使用dfs跑负环.

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 1000000000
#define ld long double
#define mod 998244353
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define rep(p,n,i) for(ll i=p;i<=n;++i)
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=5010;
int n,m,flag,t,h,len;
int vis[MAXN],q[MAXN*MAXN],dis[MAXN],w[MAXN];
int lin[MAXN],ver[MAXN],nex[MAXN],e[MAXN];
inline void add(int x,int y,int z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline void spfa()
{
    for(int i=1;i<=n;++i)q[++t]=i,vis[i]=1;
    while(++h<=t)
    {
        int x=q[h];vis[x]=0;
        for(int i=lin[x];i;i=nex[i])
        {
            int tn=ver[i];
            if(dis[tn]>dis[x]+e[i])
            {
                dis[tn]=dis[x]+e[i];
                w[tn]=w[x]+1;
                if(w[tn]==n){flag=1;break;}
                if(!vis[tn])q[++t]=tn,vis[tn]=1;
            }
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int x,y,z;
        x=read();y=read();z=read();
        add(y,x,z);
    }
    spfa();
    if(flag){puts("NO");return 0;}
    for(int i=1;i<=n;++i)printf("%d ",dis[i]);
    return 0;
}
View Code

LINK:POI2012 Festival

这道题真ex 题目描述非常不好 让人很多地方都要误解。首先是第二个约束条件是<=而不是<

然后是 最多的解的个数但是不能随便取只能取没取过的 我也不好描述 按照样例自己意会。

做法:求最多的个数 那么我们普通跑差分约束就不行了 考虑设出来状态 f[i][j]表示i最多比j多多少。

floyd转移即可 当然也可以spfa转移不过要记录一下前驱 需要dfs转移?floyd能过。

不过不能直接暴力的跑 要在每个强连通分量中跑 不同的强连通分量显然联系不大 因为连边问题。

所以答案=各个强连通分量里的最长路+scc个数.ex死我了调了半天。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 1000000000
#define ld long double
#define mod 998244353
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define rep(p,n,i) for(ll i=p;i<=n;++i)
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=1010,maxn=100010;
int n,m,len,top,cnt,id,ans;
int vis[MAXN],low[MAXN],s[MAXN],dfn[MAXN],scc[MAXN];
int mx[MAXN],dis[MAXN][MAXN];//dis[i][j]表示i最多比j快多少
int lin[MAXN],ver[maxn<<1],nex[maxn<<1];
inline void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
inline void dfs(int x)
{
    vis[x]=1;s[++top]=x;
    dfn[x]=low[x]=++cnt;
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(!dfn[tn])
        {
            dfs(tn);
            low[x]=min(low[x],low[tn]);
        }
        else if(vis[tn])low[x]=min(low[x],dfn[tn]);
    }
    if(dfn[x]==low[x])
    {
        ++id;int y;
        do
        {
            y=s[top--];
            vis[y]=0;
            scc[y]=id;
        }while(x!=y);
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    memset(dis,0x3f,sizeof(dis));
    n=read();m=read();int m1=read();
    for(int i=1;i<=m;++i)
    {
        int x,y;
        x=read();y=read();
        add(x,y);add(y,x);
        dis[x][y]=min(dis[x][y],1);
        dis[y][x]=min(dis[y][x],-1);
    }
    for(int i=1;i<=m1;++i)
    {
        int x,y;
        x=read();y=read();
        dis[y][x]=min(dis[y][x],0);
        add(y,x);
    }
    for(int i=1;i<=n;++i)if(!dfn[i])dfs(i);
    for(int k=1;k<=n;++k)
    {
        for(int i=1;i<=n;++i)
        {
            if(scc[i]!=scc[k])continue;
            for(int j=1;j<=n;++j)
            {
                if(scc[i]!=scc[j])continue;
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            }
        }
    }
    //cout<<dis[3][2]<<endl;
    for(int i=1;i<=n;++i)if(dis[i][i]<0){puts("NIE");return 0;}
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
        {
            if(scc[i]!=scc[j]||i==j)continue;
            mx[scc[i]]=max(mx[scc[i]],dis[i][j]);
        }
    for(int i=1;i<=id;++i)ans+=mx[i];
    printf("%d\n",ans+id);
    return 0;
}
View Code

 LINK:luogu4878USACOlayout 啥目啊 题目描述是个鬼两个奶牛相聚不是互相的么?建图只建单向的是什么鬼。。

然后 不需要上面一道题一样 跑floyd 或者奇怪的spfa 因为起点和终点已经给出直接用即可.坑点不是不连通 而是不连通的其他图内有不合法情况了。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 0x3f3f3f3f
#define ld long double
#define mod 998244353
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define rep(p,n,i) for(ll i=p;i<=n;++i)
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
const int MAXN=1010,maxn=10100;
int n,m,flag,t,h,len;
int vis[MAXN],q[MAXN*MAXN],dis[MAXN],w[MAXN];
int lin[MAXN],ver[maxn<<1],nex[maxn<<1],e[maxn<<1];
inline void add(int x,int y,int z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline void spfa(int s)
{
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;vis[s]=1;t=h=0;
    q[++t]=s;
    while(++h<=t)
    {
        int x=q[h];vis[x]=0;
        for(int i=lin[x];i;i=nex[i])
        {
            int tn=ver[i];
            if(dis[tn]>dis[x]+e[i])
            {
                dis[tn]=dis[x]+e[i];
                w[tn]=w[x]+1;
                if(w[tn]==n+1){puts("-1");exit(0);}
                if(!vis[tn])q[++t]=tn,vis[tn]=1;
            }
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();int m1=read();
    for(int i=1;i<=m;++i)
    {
        int x,y,z;
        x=read();y=read();z=read();
        add(x,y,z);
    }
    for(int i=1;i<=m1;++i)
    {
        int x,y,z;
        x=read();y=read();z=read();
        add(y,x,-z);
    }
    for(int i=1;i<=n;++i)add(0,i,0);
    spfa(0);spfa(1);
    if(dis[n]==INF)puts("-2");
    else printf("%d\n",dis[n]);
    return 0;
}
View Code

 LINK:luogu4246 SHOI 堵塞的交通

这是一道曾经让我非常自闭的题目 码了250行线段树最终无奈自闭。。现在再做一遍吧..

我们考虑利用线段树来维护连通性 一个点有两个元素 上面和下面,而线段树每个区间维护什么则需要斟酌一下 这询问有关。

考虑 询问了一个区间 左上 右下 我们不难发现我们最后要找到这两者的连通性 所以一个区间需要维护左上 左下 右上 右下两两之间的连通性 我们发现这样做完 查询即可.

令我没想到的是 这样还不行 简单画一下图 可以发现 还需要维护一下上下的关系 上下可以通过诡异的方式相连 所以需要维护 6个变量. 怎么说 这道题目的细节过于繁杂。而且稍有不慎 就可能打错什么的。包括在考场上 这道题是不能这样写的 这样写会崩盘。不如暴力。

现在考虑一种比较简单的做法:线段树分治 这道题是一个动态图的连通性问题我们考虑线段树分治维护+启发式合并并查集即可。

复杂度$n(logn)^2$并且可离线啊 所以这样做就行了 当然可以LCT 少一个Log 不过写起来太过鬼畜。这里还是使用线段树分治的做法吧.

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime> 
#include<cstring>
#include<string>
#include<ctime>
#include<cctype>
#include<cstdio>
#include<utility>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<algorithm>
#include<cstdlib>
#define l(p) t[p].l
#define r(p) t[p].r
#define zz p<<1
#define yy p<<1|1
#define pii pair<int,int> 
#define mk make_pair
#define pb push_back
#define F first
#define S second
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
//SHOI 2008 线段树分治 Author:Chdy
const int MAXN=100010;
int n,cnt,R;
char a[10];
int f[MAXN<<1],sz[MAXN<<1];
map<pii,int> H;
map<pii,int>::iterator it;
pii q[MAXN*20];
struct wy
{
    int l,r;
    vector<pii>g;
}t[MAXN<<2];
struct jl
{
    int x,y;
    int op;
}s[MAXN];
inline int getfather(int x){return x==f[x]?x:getfather(f[x]);}
inline void build(int p,int l,int r)
{
    l(p)=l;r(p)=r;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(zz,l,mid);
    build(yy,mid+1,r);
}
inline void insert(int p,int l,int r,pii w)
{
    if(l<=l(p)&&r>=r(p))
    {
        t[p].g.pb(w);
        return;
    }
    int mid=(l(p)+r(p))>>1;
    if(l<=mid)insert(zz,l,r,w);
    if(r>mid)insert(yy,l,r,w);
    return;
}
inline void dfs(int p)
{
    int ww=R;
    if(t[p].g.size())
    {
        for(unsigned i=0;i<t[p].g.size();++i)
        {
            int x=t[p].g[i].F;
            int y=t[p].g[i].S;
            int w1=getfather(x);
            int w2=getfather(y);
            if(w1==w2)continue;
            if(sz[w1]>sz[w2])
            {
                f[w2]=w1;
                sz[w1]+=sz[w2];
                q[++R]=mk(w1,w2);
            }
            else
            {
                f[w1]=w2;
                sz[w2]+=sz[w1];
                q[++R]=mk(w2,w1);
            }
        }
    }
    //cout<<l(p)<<' '<<r(p)<<(l(p)+r(p)>>1)<<endl;
    if(l(p)==r(p))
    {
        if(!s[l(p)].op)
        {
            if(getfather(s[l(p)].x)!=getfather(s[l(p)].y))puts("N");
            else puts("Y");
        }
        while(R!=ww)
        {
            int w1=q[R].F;
            int w2=q[R].S;
            f[w2]=w2;
            sz[w1]-=sz[w2];
            --R;
        }
        return;
    }
    dfs(zz);
    dfs(yy);
    while(R!=ww)
    {
        int w1=q[R].F;
        int w2=q[R].S;
        f[w2]=w2;
        sz[w1]-=sz[w2];
        --R;
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=2*n;++i)f[i]=i,sz[i]=1;
    while(1)
    {
        scanf("%s",a+1);
        if(a[1]=='E')break;
        int x,y,L,R;
        x=read();y=read();L=read();R=read();
        s[++cnt]=(jl){(x-1)*n+y,(L-1)*n+R,0};
        if(s[cnt].x>s[cnt].y)swap(s[cnt].x,s[cnt].y);
        if(a[1]=='O')s[cnt].op=1;
        if(a[1]=='C')s[cnt].op=2;
    }
    build(1,1,cnt);
    for(int i=1;i<=cnt;++i)
    {
        if(!s[i].op)continue;
        pii w=mk(s[i].x,s[i].y);
        if(H[w])
        {
            insert(1,H[w],i,w);
            H[w]=0;
        }
        else H[w]=i;
    }
    for(it=H.begin();it!=H.end();++it)
        if((*it).S)
            insert(1,(*it).S,cnt,(*it).F);
    dfs(1);return 0;
}
View Code

 LINK:洗牌机HNOI2001 这道题不算很困难 但是知道怎么做还需要一些置换的知识。

我们只对于一个排列来说每次进行洗牌(具体过程不再赘述... 这相当于进行置换的平方(我不懂啊。。。

好吧 存在置换总没问题吧 有若干个置换群 并且我们可以求出其周期 求出周期之后我们可以就可以倒着跑了  记录一下在某个状态的时候的上一个位置到模拟这个过程。

可能空间开销挺大的 也没必要(这个做法是基于我们暴力的优化  先找到上一个是谁 然后再倒着跑。

设周期为T 不难发现s可以%T 然后最后想当于我们要跑 s-T次倒着的设最终到达的状态为w 那不想当于跑T-s正着么..求出T以后正着跑即可。

可以发现T的大小不超过n.(我感觉是这样的。但不会证明。

//#include<bits\stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
#define db double
#define INF 2000000010
#define ld long double
#define mod 1004535809
#define pb push_back
#define put(x) printf("%d\n",x)
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define rep(p,n,i) for(int i=p;i<=n;++i)
#define zz p<<1
#define yy p<<1|1
#define sum(p) t[p].sum
#define l(p) t[p].l
#define r(p) t[p].r
#define tag(p) t[p].tag
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=1010;
int n,s,T;
int a[MAXN],b[MAXN],c[MAXN],w[MAXN];
int main()
{
    freopen("1.in","r",stdin);
    n=read();s=read();
    for(int i=1;i<=n;++i)w[i]=c[i]=a[i]=b[i]=read();
    while(1)
    {
        for(int i=1;i<=n;++i)b[i]=w[c[i]];
        int flag=0;++T;
        for(int i=1;i<=n;++i)
        {
            if(b[i]!=a[i])flag=1;
            w[i]=c[i]=b[i];
        }
        if(!flag)break;
    }
    s=s%T;s=T-s;
    for(int cc=1;cc<=s;++cc)
    {
        for(int i=1;i<=n;++i)b[i]=w[c[i]];
        for(int i=1;i<=n;++i)c[i]=w[i]=b[i];
    }
    for(int i=1;i<=n;++i)printf("%d ",b[i]);
    return 0;
}
View Code

其实还有几个问题我没搞清楚 :置换的大小 置换一定存在么 置换不存在怎么办 这好像都是跟群论知识有关但是我真的看不太懂网上的blog...

LINK:NOI2015老司机和小园丁 有一说一这题确实难写。细节有很多 不仔细规划很难在有限的时间内写出来。

现在我写一下这道题的思路 来规划一下到底怎么写比较好。仔细读题。

发现第二问是根据第一问做的 但是存在的问题是 两问并非都是可以单独考虑的 要两问一起考虑 譬如说我 写第一问的时候开map写的爽了但是第二问建图的时候傻眼了..

两问要一起思考 考虑一下第一问写出什么样的转移方式第二问会好写很多。。

对于第一问还挺显然的 首先是进行不同y的转移 再进行同y的转移这两个转移还是挺好写的,如何对正上方斜方向转移呢?

可以发现左斜的话 x+y是定值 正上的话x相同y不同 右上的话x-y是定值。我们直接用map存即可转移且这样显然不会出现不合法情况。

考虑第二问 求所有可能的最优解中非左右的方向上用的压路机最少。我们考虑把所有最优解的方案找出来进行观察。

建成图 可以发现这是一个有源汇上下界最小流。这个东西在我另外一篇文章中写过。

如何把图建出来 显然我们已知最优解后再进行dp一次开始建图。

这个时候map就有点GG了我们查不出以前的决策了 就算能记录决策但是我们不可能把所有决策都记录下来吧所以此时考虑不用map转移。

确实我们可以把从前的三个决策全部记录下来正推即可。

思考要认真 仔细。证明自己够格NOI.

 

posted @ 2019-09-19 22:01  chdy  阅读(637)  评论(0编辑  收藏  举报