2020杭电多校训练(第一、二场)

第一场

1005.Fibonacci-Sum

标签:二次剩余、二项式展开

已知斐波那契数列通项公式 F n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] Fn=\frac{1}{\sqrt{5}}[(\frac{1+\sqrt{5}}{2})^n-(\frac{1-\sqrt{5}}{2})^n] Fn=5 1[(21+5 )n(215 )n]

把常数用字母代替就是 F n = d [ a n − b n ] F_n=d[a^n-b^n] Fn=d[anbn]

然后把要求的每一项二项式展成k+1项 ( F n c ) K = C n 0 ( − 1 ) n a 0 b n + C n 1 ( − 1 ) n a 1 b n − 1 + . . . + C n n ( − 1 ) 0 a n b 0 (F_{nc})^K=C_n^0(-1)^na^0b^n+C_n^1(-1)^na^1b^{n-1}+...+C_n^n(-1)^0a^nb^0 (Fnc)K=Cn0(1)na0bn+Cn1(1)na1bn1+...+Cnn(1)0anb0把展开后二项式系数相同的项合并起来求和,就是一个等比数列求和的问题。

重点在于如何在模意义下表示根号,比如要表示模意义下的 5 \sqrt{5} 5

那也就是解 x 2 = 5 ( m o d   1 e 9 + 9 ) x^2=5(mod\ 1e9+9) x2=5(mod 1e9+9) 当然前提是5是1e9+9的二次剩余,这一部分是二次剩余的内容。

但从做题来说其实可以暴力求x,然后直接把x作为常数引入。求出x之后d就是x的逆元,a和b也能用x表示出来了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD=1e9+9,N=1e5+5;

int qpower(int x,ll p)
{
    int ret=1;
    for (int base=x;p;base=(1ll*base*base)%MOD,p=p>>1)
        if (p&1) ret=(1ll*ret*base)%MOD;
    return ret;
}
int T,k;

ll n,c;

int inv[N];

int mul(ll x,ll y) {
    x%=MOD,y%=MOD;
    return (1ll*x*y)%MOD;
}
int main()
{
    cin>>T;
    for (int i=1;i<=100001;i++) inv[i]=qpower(i,MOD-2);
    int d=qpower(383008016,MOD-2);//1/sqrt(5)
    int A=((1ll*1+383008016)*inv[2])%MOD;//(1+sqrt(5))/2
    int B=((1ll*1-383008016+MOD)*inv[2])%MOD;//(1-sqrt(5))/2
    while(T--)
    {
        cin>>n>>c>>k;
        int AA=qpower(A,c);
        int BB=qpower(B,c);
        int invAA=qpower(AA,MOD-2);
        int invBB=qpower(BB,MOD-2);
        int com=1;
        int q=qpower(AA,k);
        int QQ=qpower(q,n);
        int change=qpower(mul(BB,invAA),n);
        int a1=q;
        int sum=0;
        for (int i=0;i<=k;i++)
        {
            int tmp;
            if (q!=1) tmp=mul(a1,mul((QQ-1+MOD)%MOD,qpower((q-1+MOD)%MOD,MOD-2)));
            else tmp=mul(n,a1);
            int tt=mul(com,tmp);
            if (i%2==1) tt=MOD-tt;
            sum=(1ll*sum+tt)%MOD;
            QQ=(1ll*QQ*change)%MOD;
            a1=mul(mul(a1,BB),invAA);
            q=mul(mul(q,BB),invAA);
            if (i!=k) com=mul(mul(com,k-i),inv[i+1]);
        }
        printf("%d\n",mul(sum,qpower(d,k)));
    }
    return 0;
}

1009.Leading-Robots

标签:单调栈

按机器人位置从大到小排序后维护一个单调栈,存放当前可能成为第一名的机器人。记录机器人成为第一名的时间,如果当前机器人与栈顶机器人相遇的时间比栈顶机器人成为第一名的时间早,说明栈顶在成为第一名之前就被超越了,则将栈顶弹出。由于 s = 1 2 a t 2 s=\frac{1}{2}at^2 s=21at2,所以可以通过比较 Δ s Δ a \frac{\Delta s}{\Delta a} ΔaΔs来比较时间。最终还留在栈中的元素数量就是答案。

每个机器人最多进栈出栈一次,所以复杂度为O(n)。

需要特别处理位置和加速度都相同的情况,但不能直接删去。比较好的做法是先用一个tag数组标记一下,然后最后统计答案是检查是否有tag,若有tag则不计入答案。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1e5+5,INF=0x3f3f3f3f;

const double eps=1e-4;

int T,n,m,x,y;
struct Robot{
    int p, a;
}A[N];
struct status{
    double t;
    int a, p, id;
};
bool tag[N];

vector<status>st;
int main()
{
    cin>>T;
    while(T--)
    {
        cin>>n;
        for (int i=1;i<=n;i++) scanf("%d %d",&A[i].p,&A[i].a);
        sort(A+1,A+1+n,[](Robot x,Robot y){
            if (x.p!=y.p) return x.p>y.p;
            else return x.a>y.a;
        });
        memset(tag,0,sizeof(tag));
        for (int i=1;i<=n-1;i++)
        {
            if (A[i].a==A[i+1].a && A[i].p==A[i+1].p) tag[i]=tag[i+1]=1;
        }
        st.clear();
        st.push_back({0,A[1].a,A[1].p,1});
        for (int i=2;i<=n;i++)
        {
            status now=st.back();
            if (A[i].a<=now.a) continue;
            double t=1.0*(now.p-A[i].p)/(A[i].a-now.a);
            while (fabs(t-now.t)<eps || t<now.t)
            {
                st.pop_back();
                now=st.back();
                t=1.0*(now.p-A[i].p)/(A[i].a-now.a);
            }
            st.push_back({t,A[i].a,A[i].p,i});
        }
        int ans=0;
        for (auto x:st) if (!tag[x.id]) ans++;
        printf("%d\n",ans);
    }
    return 0;
}

1006.Finding-a-MEX

标签:分块,树状数组求MEX

根据题意可以很快想到两种解决方法

  1. 单点修改+询问时遍历所有邻点
  2. 修改时修改邻点的树状数组+询问时查询树状数组

两种做法都有很明显的缺陷,第一种如果反复询问某个大结点(邻点非常多),第二种如果反复修改大结点,都肯定会T

于是比较巧妙的通过分块来稳定时间复杂度。首先定义度数大于某个临界值的为超级点,(可以取 2 m = 450 \sqrt{2m}=450 2m =450),其他为小结点(可知超级点个数不超过450)。

每次修改时,单点修改+修改相邻超级点的树状数组。

每次查询时,若该点为超级点,直接通过树状数组查询MEX,若该店为小结点,遍历邻点即可。

在树状数组记录MEX时,对于同一个数只需要记录一次,可以在树状数组的基础上再开一个桶数组,记录某个数出现的次数,只有当出现次数由0变为1或由1变为0时,才对树状数组产生修改。

通过对树状数组二分的方式求MEX。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;

struct BIT{
	int SZ;
    vector<int>tr;
    vector<int>t;
    int lowbit(int x){ return x&(-x); }
    void init(){
		tr.resize(SZ);
		t.resize(SZ);
    }
    void add(int x,int v){
    	if (x<SZ)
		{
			t[x]+=v;
			if (t[x]==1 && v==1|| t[x]==0 && v==-1)
				while(x<SZ)
				{
					tr[x]+=v;
					x+=lowbit(x);
				}
		}
    }
    int sum(int x){
        int sum=0;
        while(x>0)
        {
            sum+=tr[x];
            x-=lowbit(x);
        }
        return sum;
    }
    int MEX(){
		int l=1,r=SZ-1;
		while(l<r)
		{
			int mid=(l+r)>>1;
			if (sum(mid)<mid)
				r=mid;
			else l=mid+1;
		}
		return l;
    }
}bit[N];

int T,n,m,u,v,x,op,q;

vector<int>G[N];

vector<int>Super[N];

int A[N];

int deg[N];

void init()
{
	for (int i=1;i<=n;i++)
	{
		G[i].clear();
		Super[i].clear();
		deg[i]=0;
		bit[i].tr.clear();
		bit[i].t.clear();
		bit[i].SZ=0;
	}
}

int main()
{
	//freopen("finding-a-mex.in","r",stdin);
	//freopen("finding-a-mex.out","w",stdout);
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		init();
		for (int i=1;i<=n;i++)
		{
			scanf("%d",&A[i]);
			A[i]++;
		}
		for (int i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
			G[v].push_back(u);
			deg[u]++,deg[v]++;
		}
		for (int i=1;i<=n;i++)
		{
			if (deg[i]>=1){
				bit[i].SZ=deg[i]+3;
				bit[i].init();
			}
		}
		for (int i=1;i<=n;i++)
		{
			for (int to:G[i])
			{
				if (deg[to]>=1)
				{
					Super[i].push_back(to);
					bit[to].add(A[i],1);
				}
			}
		}
		cin>>q;
		while(q--)
		{
			scanf("%d",&op);
			if (op==1)
			{
				scanf("%d%d",&u,&x);
				x++;
				for (auto to:Super[u])
				{
					bit[to].add(A[u],-1);
					bit[to].add(x,1);
				}
				A[u]=x;
			}
			else
			{
				scanf("%d",&x);
				int ans=1;
				if (deg[x]>=1)
					ans=bit[x].MEX();
				else
				{
					vector<int>now;
					for (auto to:G[x])
					{
						now.push_back(A[to]);
					}
					sort(now.begin(),now.end());
					for (auto p:now)
					{
						if (p>ans) break;
						ans=p+1;
					}
				}
				printf("%d\n",ans-1);
			}
		}
	}
	return 0;
}

第二场

1012.String-Distance

标签:dp求LCS

因为插入操作可以被删除操作替代,所以问题也就转化成求A、B两串的最长公共子序列(LCS)

先预处理出g[i][c]表示[ai,ai+1,…,an]中第一个字符c的下标

dp[i][j]表示用B串前i位匹配到j个字符时 在A串中最小的位置

然后就可以O(m^2)转移了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1e5+5,INF=0x3f3f3f3f;

int T,q,l,r;

char s[N],t[25];

vector<int>G[30];

int dp[25][25];
int g[N][30];
int ans[N];
struct query{
    int l,r,id;
}Q[N];
int main()
{
    cin>>T;
    while(T--)
    {
        scanf("%s",s+1);
        scanf("%s",t+1);
        int ls=strlen(s+1),lt=strlen(t+1);
        for (int j=0;j<=25;j++) g[ls+1][j]=INF;
        for (int i=ls;i>=1;i--)
        {
            for (int j=0;j<=25;j++)
                if ((s[i]-'a')==j) g[i][j]=i;
                else
                {
                    g[i][j]=g[i+1][j];
                }
        }
        cin>>q;
        while(q--)
        {
            scanf("%d%d",&l,&r);
            memset(dp,INF,sizeof(dp));
            int mx=0;
            for (int i=0;i<=lt;i++) dp[i][0]=l-1;
            for (int i=1;i<=lt;i++)
            {
                if (g[dp[i-1][0]+1][t[i]-'a']<=r)
                dp[i][1]=min(dp[i-1][1],g[dp[i-1][0]+1][t[i]-'a']);
                else
                    dp[i][1]=dp[i-1][1];
                if (dp[i][1]!=INF) mx=max(mx,1);
            }
            for (int i=2;i<=lt;i++)
                for (int j=2;j<=i;j++)
                {
                    if (dp[i-1][j-1]!=INF && g[dp[i-1][j-1]+1][t[i]-'a']<=r)
                    dp[i][j]=min(dp[i-1][j],g[dp[i-1][j-1]+1][t[i]-'a']);
                    else
                        dp[i][j]=dp[i-1][j];
                    if (dp[i][j]!=INF) mx=max(mx,j);
                }
            printf("%d\n",r-l+1+lt-2*mx);
        }
    }
    return 0;
}

1005.New-Equipments

把问题转换成n个二次函数图像,比较重要的是观察到每个工人只需要考虑对称轴附近的50个数,那么最多只需要考虑2500个数就可以了。

把所有工人作为一个点和源点连边,把需要考虑的数与汇点连边,再把每个工人与其需要考虑的数连边,距离为花费。

然后开始跑最小费用最大流,每找到一条增广路就记录一次答案。

#include<bits/stdc++.h>
#define debug(x) cerr<<#x<<" : "<<x<<endl;
using namespace std;
typedef long long ll;

const int MAXN=11000;

int T,n,m,top;

ll ans;

bool vis[MAXN];

int s,t,pre[MAXN],last[MAXN],flow[MAXN],maxflow;

ll dis[MAXN],mincost;

struct Edge{
    int to,next,flow;//flow流量 dis花费
    ll dis;
}edge[MAXN];
int head[MAXN],num_edge;
queue <int> q_SPFA;
void add_edge(int from,int to,int flow,ll dis)
{
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    edge[num_edge].flow=flow;
    edge[num_edge].dis=dis;
    head[from]=num_edge;
}
bool spfa(int s,int t)
{
    memset(dis,0x7f,sizeof(dis));
    memset(flow,0x7f,sizeof(flow));
    memset(vis,0,sizeof(vis));
    q_SPFA.push(s); vis[s]=1; dis[s]=0; pre[t]=-1;
    while (!q_SPFA.empty())
    {
        int now=q_SPFA.front();
        q_SPFA.pop();
        vis[now]=0;
        for (int i=head[now]; i!=-1; i=edge[i].next)
        {
            if (edge[i].flow>0 && dis[edge[i].to]>dis[now]+edge[i].dis)//正边
            {
                dis[edge[i].to]=dis[now]+edge[i].dis;
                pre[edge[i].to]=now;
                last[edge[i].to]=i;
                flow[edge[i].to]=min(flow[now],edge[i].flow);
                if (!vis[edge[i].to])
                {
                    vis[edge[i].to]=1;
                    q_SPFA.push(edge[i].to);
                }
            }
        }
    }
    return pre[t]!=-1;
}
void MCMF()
{
    while (spfa(s,t))
    {
        int now=t;
        maxflow+=flow[t];
        mincost+=1ll*flow[t]*dis[t];
        top++;
        printf("%lld%c",mincost,top==n?'\n':' ');
        if (top==n) return;
        while (now!=s)
        {
            edge[last[now]].flow-=flow[t];
            edge[last[now]^1].flow+=flow[t];
            now=pre[now];
        }
    }
}

ll a[55],b[55],c[55];

set<int>S;
vector<int>G[55];
map<int,int>ord;

void init()
{
	top=0;
	num_edge=-1;
	memset(head,-1,sizeof(head));
	memset(edge,0,sizeof(edge));
	S.clear();
	for (int i=1;i<=n;i++) G[i].clear();
	ord.clear();
	maxflow=mincost=0;
}

void sol()
{
	cin>>n>>m;
	init();
	for (int i=1;i<=n;i++)
	{
		scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
		int mid=b[i]/(-2*a[i]);
		int l=mid-25,r=mid+25;
		if (r<1)
		{
			l=1,r=min(m,50);
		}
		else
		{
			if (l>m)
			{
				r=m;l=max(1,r-49);
			}
			else
			{
				if (l<1)
				{
					r+=1-l;
					l=1;
				}
				if (r>m)
				{
					l-=r-m;
					r=m;
				}
				if (l<1) l=1;
			}
		}
		for (int j=l;j<=r;j++)
		{
			S.insert(j);
			G[i].push_back(j);
		}
	}
	s=0,t=n+S.size()+1;
	for (int i=1;i<=n;i++)
	{
		add_edge(s,i,1,0);
		add_edge(i,s,0,0);
	}
	int tmp=0;
	for (auto x:S)
	{
		ord[x]=++tmp;
		add_edge(n+ord[x],t,1,0);
		add_edge(t,n+ord[x],0,0);
	}

	for (int i=1;i<=n;i++)
	{
 		for (auto to:G[i])
		{
			add_edge(i,n+ord[to],1,(a[i]*to*to+b[i]*to+c[i]));
 			add_edge(n+ord[to],i,0,-1ll*(a[i]*to*to+b[i]*to+c[i]));
		}
	}
	MCMF();
}
int main()
{
	cin>>T;
	while(T--)
		sol();
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值