2019.06.01 【NOIP提高组】模拟 A 组

前言

这可是六一呀


JZOJ 6191 Exchange

题目

在这里插入图片描述


分析

首先可以预处理出一个位置的后继,这个可以通过类似于链表的结构实现,接着这就是关键了,这样貌似并没有什么意义,把这些区间按右端点从小到大排序离线询问,那只要预处理一个点的后继能散播到最靠前的那个点,那也就是这个区间可以整体交换次数加1,那么对于每个询问,输出区间中的最大值,用线段树解决


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
const int N=1500101; struct rec{int l,r,rk;}b[N];
struct segt{
......
}zkw;
int a[N],n,m,st[N],ans[N],nx[N];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans; 
}
bool cmp(rec x,rec y){return x.r<y.r;}
void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48); 
}
signed main(){
	n=iut(); m=iut(); a[++n]=2147483647;
	for (rr int i=1;i<n;++i) a[i]=iut();
	for (rr int i=n-1;i;--i){
		nx[i]=i+1,st[i]=i;
		while (a[i]>=a[nx[i]]) nx[i]=nx[nx[i]];
	} st[n]=n;
	for (rr int i=1;i<=n;++i)
	    st[nx[i]]=min(st[nx[i]],st[i]);;
	for (rr int i=1;i<=m;++i) b[i]=(rec){iut(),iut(),i};
	sort(b+1,b+1+m,cmp); for (zkw.bas=1;(zkw.bas<<=1)<n+2;);
	for (rr int i=1,t=0;i<=m;++i){
		while (t<b[i].r) ++t,zkw.update(st[t],t);
        ans[b[i].rk]=zkw.answ(b[i].l,b[i].r);
	}
	for (rr int i=1;i<=m;++i) print(ans[i]),putchar(10);
	return 0;
}

JZOJ 4802 探险计划

分析

就是道费用流的题目,如果点不重复走,则要拆点;能重复走,则不需要拆点,其它其实没什么好说的


代码

#include <cstdio>
#include <deque>
#include <cstring>
#define rr register
#define id(x,y) ((x-1)*m+y)
using namespace std;
const int inf=707406378;
struct node{int y,w,f,next;}e[500001]; bool v[20001];
int k=1,ls[20001],s,t,s1,cnt,ans,n,m,dis[20001],a[81][161],h[81][161];
inline void add(int x,int y,int w,int f){
    e[++k]=(node){y,w,f,ls[x]}; ls[x]=k;
    e[++k]=(node){x,0,-f,ls[y]}; ls[y]=k;
}
inline signed spfa(){
    memset(v,0,sizeof(v)); memset(dis,127/3,sizeof(dis));
    dis[t]=0; v[t]=1; rr deque<int>q; q.push_back(t);
    while (q.size()){
        rr int x=q.front(); q.pop_front();
        for (rr int i=ls[x];i;i=e[i].next)
        if (e[i^1].w&&dis[e[i].y]>dis[x]-e[i].f){
            dis[e[i].y]=dis[x]-e[i].f;
            if (!v[e[i].y]){
                v[e[i].y]=1;
                if (q.size()&&dis[e[i].y]<dis[q.front()])
                q.push_front(e[i].y); else q.push_back(e[i].y);
            }
        }
        v[x]=0;
    }
    return dis[s]<707406378;
}
inline signed dfs(int x,int now){
    if (x==t) {v[t]=1; return now;}
    rr int rest=0,f; v[x]=1;
    for (rr int i=ls[x];i;i=e[i].next)
    if (!v[e[i].y]&&e[i].w&&dis[e[i].y]+e[i].f==dis[x]){
        rest+=(f=dfs(e[i].y,min(e[i].w,now-rest)));
        if (f) ans+=f*e[i].f,e[i].w-=f,e[i^1].w+=f;
        if (rest==now) break;
    }
    return rest;
}
inline void answ(){
    while (spfa()){
        v[t]=1;
        while (v[t]){
            memset(v,0,sizeof(v));
            dfs(s,1e9);
        }
    }
}
signed main(){
	scanf("%d%d",&n,&m); s=cnt=1;
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<m+i;++j) scanf("%d",&a[i][j]),h[i][j]=++cnt;
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<m+i;++j){
		add(h[i][j],h[i][j]+cnt,1,a[i][j]);
		if (i>1){
		    if (j<m+i-1) add(h[i-1][j]+cnt,h[i][j],1,0);
		    if (j>1) add(h[i-1][j-1]+cnt,h[i][j],1,0);
		}else add(s,h[i][j],1,0);
	} t=cnt+1,s1=t+1; 
    for (rr int i=1;i<m+n;++i) add(h[n][i]+cnt,t,1,0);
	answ(); printf("%d\n",ans);
    memset(ls,0,sizeof(ls)); k=1; add(s,s1,m,0);
    for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<m+i;++j){
    	if (i>1){
    		if (j<m+i-1) add(h[i-1][j],h[i][j],1,a[i][j]);
    		if (j>1) add(h[i-1][j-1],h[i][j],1,a[i][j]); 
		}else add(s1,h[i][j],inf,a[i][j]);
	}
	for (rr int i=1;i<m+n;++i) add(h[n][i],t,inf,0);
	ans=0; answ();
    return !printf("%d",ans);
}

JZOJ 4800 周末晚会

题目

n n n个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过 k k k个女孩座位是连续的。


分析

首先如果没有圆桌,设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个人,末尾 j j j个是女生的合法方案数,
那么 d p [ i ] [ 0 ] = ∑ d p [ i − 1 ] [ j ] , d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] ( j ≠ 0 ) dp[i][0]=\sum dp[i-1][j],dp[i][j]=dp[i-1][j-1](j\neq 0) dp[i][0]=dp[i1][j],dp[i][j]=dp[i1][j1](j̸=0),特殊地, d p [ 1 ] [ 1 ] = 1 dp[1][1]=1 dp[1][1]=1,若 n ≤ k n\leq k nk再把它加上,因为若第一个是女生会在后面的计算中产生错误,所以要特殊处理
之后设 c [ i ] c[i] c[i]表示前 i i i个人放在圆桌不考虑重复的合法方案数,那么 c [ i ] = ∑ j = 0 k d p [ i ] [ j ] ∗ ( j + 1 ) c[i]=\sum_{j=0}^kdp[i][j]*(j+1) c[i]=j=0kdp[i][j](j+1)
再设 p [ i ] p[i] p[i]表示前 i i i个人在 c [ i ] c[i] c[i]的前提下没有循环节的合法方案数,那么 p [ i ] = c [ i ] − ∑ j ∣ i , j ≠ i c [ j ] p[i]=c[i]-\sum_{j|i,j\neq i}c[j] p[i]=c[i]ji,j̸=ic[j]
那么答案就是 ∑ d ∣ n p [ d ] d \sum_{d|n}\frac{p[d]}{d} dndp[d]


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
const int mod=1e8+7;
int inv[2001],dp[2001][2001],f[2001],s[2001];
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans;
}
inline signed mo(int x){return x>=mod?x-mod:x;}
signed main(){
    inv[0]=inv[1]=1;
    for (rr int i=2;i<2001;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    for (rr int t=iut();t;--t){
        rr int n=iut(),k=iut();
        memset(dp,0,sizeof(dp)); dp[1][0]=1;
        for (rr int i=1;i<n;++i)
        for (rr int j=i-1<k?i-1:k;~j;--j)
            dp[i+1][0]=mo(dp[i+1][0]+dp[i][j]),dp[i+1][j+1]=dp[i][j];
        memset(f,0,sizeof(f));
        for (rr int i=1;i<=n;++i)
        for (rr int j=0;j<=k;++j)
        f[i]=mo(f[i]+1ll*dp[i][j]*(j+1)%mod);
        for (rr int i=1;i<=n;++i){
            s[i]=mo(f[i]-s[1]*(i>1)+mod);
            for (rr int j=2;j*j<=i;++j)
            if (!(i%j)){
                s[i]=mo(s[i]-s[j]+mod);
                if (j*j<i) s[i]=mo(s[i]-s[i/j]+mod);
            }
        }
        rr int ans=n<=k;
        for (rr int i=1;i*i<=n;++i)
        if (!(n%i)){
            ans=mo(ans+1ll*s[i]*inv[i]%mod);
            if (i*i<n) ans=mo(ans+1ll*s[n/i]*inv[n/i]%mod);
        }
        printf("%d\n",ans);
    }
    return 0;
}

后续

啊啊啊,好难啊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值