123789456ye

已AFO

CSP前停课记录

11.7 \(n\)校联考模拟赛

猜结论大赛……

T1

题意:给定\(n\),求把\(n\)分解成\(k\)堆,每堆数量形如\(3n^2-3n+1,1\leq n\),的最小堆数。\(T\)组数据。\(n\leq 10^{11},T\leq 10^3\)
题解:首先有两个结论
1.\(ans\leq 8\)
证明忘了
2.\(ans\equiv n~(mod~6)\)
这是因为$$3n^2-3n+1\equiv 1(mod6)$$
所以就可以随意乱搞上代码了
复杂度:\(O(T\sqrt n \log n)\),但常数很小(只有模等于\(2\)需要判一下,并且要log的数远小于\(n\)

#include<bits/stdc++.h>
using namespace std;
long long tp,f[400005],n;
inline bool check(long long x)
{
	if((x-1)%3!=0) return 0;
	--x,x/=3;
	long long z=sqrt(x);
	return z*(z+1)==x;
}
int main()
{
	//freopen("stone.in","r",stdin),freopen("stone.out","w",stdout);
	int T;
	scanf("%d",&T);
	f[1]=1;
	for(int i=2;i<=400000;++i)
		tp+=6,f[i]=f[i-1]+tp;
	while(T--)
	{
		scanf("%lld",&n);
		if(n%6==1)
		{
			if(!check(n)) puts("7");
			else puts("1");//如果能直接做就是1,否则7
		}
		else if(n%6==2)
		{
			for(int i=1;i<=400000&&f[i]<n;++i)
				if(check(n-f[i]))//同上
				{
					puts("2");
					goto End;
				}
			puts("8");
		  End:;
		}
		else if(n%6==0) puts("6");
		else printf("%lld\n",n%6);
	}
	return 0;
}

T2

题意:有\(n\)堆石子,每堆石子有\(a_{i}\)个。有\(A,B,C\)三个人,按\(A,B,C\)顺序操作。
每次操作可以将某一非空堆内取任意多石头(非0)。
最后一个能操作的人为\(rk~1\),他后一个操作的为\(rk~3\),另一个人\(rk~2\)。每个人都会最小化自己的\(rk\)
给出\(n,a_{i}\),求最后\(rk~1\)是谁。
\(T\)组数据。
\(T\leq 10^3,\sum n\leq 10^6,a_{i}\leq 10^{18}\)
题解:以下为官方题解看不懂
可以发现,答案是\(C\)当且仅当每一位所有数都有\(3\)的倍数个\(0\)证明只需考虑最高位\(1\)的个数,然后大力归纳即可。简单说一下归纳的过程,就是对\(\sum a_{i}\)归纳,需要说明任意状态操作\(2\)步一定可以到达\(C\)状态。
知道这个结论后,先判\(ans=C\),否则看看能否一步操作成\(C\),这样答案就是\(A\),剩下情况为\(B\)
复杂度:\(O(\sum n* \log a_{i})\)

#include<bits/stdc++.h>//std
#define fi first
#define se second
#define pb push_back
#define SZ(x) ((int)x.size())
#define L(i,u) for (register int i=head[u]; i; i=nxt[i])
#define rep(i,a,b) for (register int i=(a); i<=(b); i++)
#define per(i,a,b) for (register int i=(a); i>=(b); i--)
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> Pii;
typedef vector<int> Vi;
template<class T> inline void read(T &x){
	x=0; char c=getchar(); int f=1;
	while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}
	while (isdigit(c)) {x=x*10+c-'0'; c=getchar();} x*=f;
}
template<class T> inline void umin(T &x, T y){x=x<y?x:y;}
template<class T> inline void umax(T &x, T y){x=x>y?x:y;}
inline ui R() {
	static ui seed=416;
	return seed^=seed>>5,seed^=seed<<17,seed^=seed>>13;
}
//const int N = ;
int n;ll a[1066666];
map<pair<Vi,int>,int>Map;
inline int getrk(int lst, int id){
	if(lst==id)return 1;
	if((lst+1)%3==id)return 3;
	return 2;
}
inline int solve(){
	int cnt[60]={0};
	rep(i,1,n)rep(j,0,59)cnt[j]+=a[i]>>j&1;
	int s=0;rep(j,0,59)cnt[j]%=3,s+=cnt[j];if(!s)return 2;
	rep(i,1,n){
		ll need=0;bool ok=1;
		rep(j,0,59){
			int x=cnt[j]-(a[i]>>j&1);x=(-x+3)%3;
			if(x==2){ok=0;break;}
			else need|=(ll)x<<j;
		}
		if(ok&&need<a[i])return 0;
	}
	return 1;
}
int main() {
		int T;read(T);while(T--){
			read(n);rep(i,1,n)read(a[i]);
			printf("%c\n",solve()+'A');
		}
	return 0;
}

T3

题意:给定\(s,t\)两个长度为\(n\)的01串,\(k\),表示你可以将连续\(k\)个相同的字符反转。求是否能有\(s\)\(t\)\(T\)组数据。\(T\leq 10^3,\sum n\leq 10^6,k\leq 10^6\)
题解:如果有连续\(k-1\)个相同的,那么你可以把两端的字符同时翻转
\(example\):

\[000001010 \to01000010,k=4 \]

所以你可以像这样把所有1尽可能往前移动,对两个串都这么做,如果变完了的两串相同就可以,否则不可以。
复杂度:\(O(n)\)

#include<bits/stdc++.h>//这是lsy2003,目前的这道题rk1的
using namespace std;
#define N 2000007
int n,m,a[N],b[N];
char s[N];
stack<int> solve(int *a)
{
    stack<int> S;
    for(int i=n+1;i>=1;i--)
    {
	    if(!a[i])continue;
	    int r=S.empty()?n+1:S.top();
	    int y=i+(r-i)/m*m;
    	if(!S.empty()&&y==S.top())S.pop();
	    else S.push(y);
    }
    return S;
}
void read(int *a)
{
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)a[i]=s[i]-'0';
    a[0]=a[n+1]=0;
    for(int i=n+1;i>=1;i--)a[i]=a[i]^a[i-1];
}
int main()
{
    int t;
    //freopen("necklace.in","r",stdin);
    //freopen("necklace.out","w",stdout);
    scanf("%d",&t);
    while(t--)
    {
	    scanf("%d%d",&n,&m);
	    read(a),read(b);
    	if(solve(a)==solve(b))printf("Yes\n");
    	else printf("No\n");
    }
    return 0;
}

11.8 \(n\)校联考模拟赛

T1

题意:求

\[\prod_{l=1}^n{\prod_{r=l}^n{lcm(a_{l},...,a_{r})}}~mod~1e9+7 \]

\(n\leq 10^5,a_{i}\leq 10^7\)
题解:分解质因数。对某一个数的某一项质数幂\(x_{i}\),设\(l\)为第一个幂次大于它的,\(r\)为右边,则它的贡献次数为\((r-i)*(i-l)\)
最后把所有质数所有幂次全部乘起来就行了。
复杂度:\(O(1e7)\)

#include <bits/stdc++.h>//std,我的不知为何一直T
using namespace std;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

typedef long long ll;
const int maxn = 100005, maxp = 1e7 + 5, mod = 1e9 + 7;

int n, a[maxn], P[maxp], cnt;
int num[maxn];
int stk[maxn], top, L[maxn], R[maxn];
vector<int> vec[maxp];

inline int fpow(int x, int k)
{
	int res = 1;
	while (k) {
		if (k & 1) res = (ll)res * x % mod;
		k >>= 1; x = (ll)x * x % mod;
	}
	return res;
}

void sieve(int n = 1e7)//线筛1e7的质数
{
	static bool vis[maxp];
	for (int i = 2; i <= n; ++i) {
		if (!vis[i]) P[++cnt] = i;
		for (int j = 1; j <= cnt && P[j] * i <= n; ++j) {
			vis[i * P[j]] = 1;
			if (i % P[j] == 0) break;
		}
	}
}

inline int cmp(int x, int y)
{
	return num[x] != num[y] ? num[x] < num[y] : x < y;
}

int main()
{
	//freopen("sphere.in","r",stdin);
	//freopen("sphere.out","w",stdout);
	n = gi();
	for (int i = 1; i <= n; ++i) a[i] = gi();
	
	sieve();

	int ans = 1, p, k, flg = 1;
	for (k = 1; k <= cnt && P[k] * P[k] < 1e7; ++k) {
		p = P[k];
		flg = 0;
		for (int i = 1; i <= n; ++i) {
			num[i] = 0;
			while (a[i] % p == 0) a[i] /= p, ++num[i], flg = 1;
		}
		if (!flg) continue;
		
		for (int i = 1; i <= n; ++i) {//求l和r,手玩一下就明白了
			while (top && cmp(stk[top], i)) R[stk[top--]] = i;
			stk[++top] = i;
		}
		while (top) R[stk[top--]] = n + 1;
		for (int i = n; i >= 1; --i) {
			while (top && cmp(stk[top], i)) L[stk[top--]] = i;
			stk[++top] = i;
		}
		while (top) L[stk[top--]] = 0;

		ll sum = 0;
		for (int i = 1; i <= n; ++i)
			sum += (ll)num[i] * (R[i] - i) * (i - L[i]);
		ans = (ll)ans * fpow(p, sum % (mod - 1)) % mod;
	}

	for (int i = 1; i <= n; ++i) 
		if (a[i] > 1) vec[a[i]].push_back(i);//剩下的大于根号1e7的质数
	
	for ( ; k <= cnt; ++k) {
		p = P[k];
		if (vec[p].empty()) continue;
		ll sum = (ll)n * (n + 1) / 2;
		int lst = 0;
		for (int siz = vec[p].size(), i = 0; i < siz; ++i) {
			sum = sum - (ll)(vec[p][i] - lst) * (vec[p][i] - lst - 1) / 2;
			lst = vec[p][i];
		}
		sum = sum - (ll)(n - lst) * (n - lst + 1) / 2;
		ans = (ll)ans * fpow(p, sum % (mod - 1)) % mod;
	}

	printf("%d\n", ans);
	
	return 0;
}

T2

题意:给定一颗\(n\)点的树,有点权有边权,求所有点对\((u,v)\)中的

\[max(dis(i,j)* gcd(i,j)* min(i,j)) \]

其中\(dis\)为两点距离,\(gcd,min\)为两点路径上所有点权的\(gcd,min\)
\(n\leq 10^5,a_{i}\leq 5* 10^4\)
题解:令人智熄的操作
枚举gcd!
保留是\(gcd\)倍数的点,按点权大小依次加入(这就固定了\(gcd,min\))。
\(dis\)用并查集维护(显然就是直径),两个并查集合并时暴力更新。
复杂度:\(O(n*d(5*10^4)*\alpha(n))\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define ll long long
struct Edge
{
	int fr,to,val;
}eg[maxn<<1];
int fa[maxn],head[maxn],edgenum;
vector<int> ve[maxn];
inline void add(int fr,int to,int val)
{
	eg[++edgenum]=(Edge){head[fr],to,val};
	head[fr]=edgenum;
}
inline int findf(int x)
{
	return fa[x]==x?x:fa[x]=findf(fa[x]);
}
int deep[maxn],size[maxn],dis[maxn],top[maxn],son[maxn],father[maxn];
int a[maxn],tpn[maxn],tpcnt,vis[maxn];
void dfs(int rt)
{
	size[rt]=1;
	int maxson=-1;
	for(int i=head[rt];i;i=eg[i].fr)
	{
		if(eg[i].to==father[rt]) continue;
		deep[eg[i].to]=deep[rt]+1;
		dis[eg[i].to]=dis[rt]+eg[i].val;
		father[eg[i].to]=rt;
		dfs(eg[i].to);
		size[rt]+=size[eg[i].to];
		if(size[eg[i].to]>maxson)
			maxson=size[eg[i].to],son[rt]=eg[i].to;
	}
}
void dfs2(int rt,int topp)
{
	top[rt]=topp;
	if(son[rt]) dfs2(son[rt],topp);
	for(int i=head[rt];i;i=eg[i].fr)
	{
		if(eg[i].to==son[rt]||eg[i].to==father[rt]) continue;
		dfs2(eg[i].to,eg[i].to);
	}
}
inline int lca(int x,int y)
{
	while(top[x]^top[y])
	{
		if(deep[top[x]]<deep[top[y]]) swap(x,y);
		x=father[top[x]];
	}
	if(deep[x]>deep[y]) swap(x,y);
	return x;
}
inline int dist(int x,int y)
{
	return dis[x]+dis[y]-(dis[lca(x,y)]<<1);
}
int diafr[maxn],diato[maxn],dialen[maxn];//直径起点,终点,长度
inline int unio(int x,int y)
{
	x=findf(x),y=findf(y);
	int f1=diafr[x],f2=diafr[y],t1=diato[x],t2=diato[y];
	int tp,nfr,nto,ndis;
	if(dialen[x]<dialen[y]) ndis=dialen[y],nfr=f2,nto=t2;//暴枚
	else ndis=dialen[x],nfr=f1,nto=t1;
	tp=dist(f1,f2); if(tp>ndis) ndis=tp,nfr=f1,nto=f2;
	tp=dist(f1,t2); if(tp>ndis) ndis=tp,nfr=f1,nto=t2;
	tp=dist(t1,f2); if(tp>ndis) ndis=tp,nfr=t1,nto=f2;
	tp=dist(t1,t2); if(tp>ndis) ndis=tp,nfr=t1,nto=t2;
	fa[x]=y;
	diafr[y]=nfr,diato[y]=nto,dialen[y]=ndis;
	return ndis;
}
inline bool cmp(const int& x,const int& y)
{
	return a[x]>a[y];
}

int main()
{
	int n,x,y,w;
	ll ans=0;
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),ve[a[i]].push_back(i);
	for(int i=1;i<n;++i)
	{
		scanf("%d%d%d",&x,&y,&w);
		add(x,y,w),add(y,x,w);
	}
	dfs(1);
	dfs2(1,1);
	for(int i=1;i<=n;++i)
		diafr[i]=diato[i]=fa[i]=i;
	for(int g=1;g<=50000;++g)
	{
		tpcnt=0;
		for(int i=g;i<=50000;i+=g)
			for(vector<int>::iterator it=ve[i].begin();it!=ve[i].end();++it)
				tpn[++tpcnt]=*it;
		sort(tpn+1,tpn+tpcnt+1,cmp);
		for(int i=1;i<=tpcnt;++i)
		{
			x=tpn[i];
			vis[x]=1;
			for(int j=head[x];j;j=eg[j].fr)
				if(vis[eg[j].to])
					ans=max(ans,1ll*g*a[x]*unio(x,eg[j].to));
		}
		for(int i=1;i<=tpcnt;++i)
			vis[tpn[i]]=dialen[tpn[i]]=0,diafr[tpn[i]]=diato[tpn[i]]=fa[tpn[i]]=tpn[i];
	}
	printf("%lld\n",ans);
	return 0;
}

T3
题意:
每一步必须\(x'>x\&\&y'>y\)
\((x,x)\to(x',y')\)代价为

\[a_{x}b_{y}+(m-\sum_{i=x+1}^{x'}a_{i})^2+(m-\sum_{j=y+1}^{y'}b_{j})^2 \]

给定\(n,m,a_{i},b_{i}\),求\((1,1)\to(n,n)\)最小代价。
\(n\leq 2* 10^3\)
题解:对每个\(j\)维护一个\(i\)的凸包
我也不知道这是什么
复杂度:\(O(n^2)\)

#include <bits/stdc++.h>//又是rk1的神仙的代码
#define _ long long

const _ inf=1000000000000000000ll;
const _ N=2010;

inline _ read() {
	_ r=0; char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) {r=(r<<1)+(r<<3)+(c^'0'); c=getchar();}
	return r;
}

struct Line {
	_ k,b;
	inline _ Get(_ x) {return k*x+b;}
};

inline double Cross(const Line &a,const Line &b) {return 1.0*(a.b-b.b)/(b.k-a.k);}

_ a[N],b[N],Suma[N],Sumb[N],f[N][N],g[N][N],n,m;

struct Node {
	Line l[N];
	_ head,tail;
	inline void Insert(Line x) {
		while((tail-head>1)&&Cross(l[tail-1],x)>Cross(l[tail-1],l[tail-2])) --tail;
		l[tail++]=x;
	}
	inline _ Get(_ x) {
		if(head==tail) return inf;
		while((tail-head>1)&&l[head].Get(x)>l[head+1].Get(x)) ++head;
		return l[head].Get(x);
	}
};

Node CF[N],CG[N];

int main() {
	n=read(); m=read();
	for(register int i=1;i<=n;++i) {a[i]=read(); Suma[i]=Suma[i-1]+a[i];}
	for(register int i=1;i<=n;++i) {b[i]=read(); Sumb[i]=Sumb[i-1]+b[i];}
	for(register int i=n;i;--i)
		for(register int j=n;j;--j) {
			if(i==n&&j<n) break;
			if(i<n||j<n) {
				if(j<n) 
				    f[i][j]=(m+Sumb[j])*(m+Sumb[j])+CG[i].Get(m+Sumb[j])+a[i]*b[j];
				if(j==n||i<n-1) 
				    g[i][j]=(m+Suma[i])*(m+Suma[i])+CF[j].Get(m+Suma[i]);
			}
			if((i<n&&j<n)||(i==n&&j==n))                     
			    CF[j].Insert({-2*Suma[i],f[i][j]+Suma[i]*Suma[i]});
			if(j==n||i<n-1) 
			    CG[i].Insert({-2*Sumb[j],g[i][j]+Sumb[j]*Sumb[j]});
		}
	printf("%lld", f[1][1]);
	return 0;
}

11.9 模拟赛

T1

题意:你有\(s\)个瓶子,可以去\(n\)个商店用\(a_{i}\)个瓶子兑换\(b_{i}\)个瓶子和一个纪念币。求最多能获得多少纪念币。可以获得无限多则输出"-1"。\(n\leq 10^5,s,a_{i},b_{i}\leq 10^{19}\)
题解:直接按\(a_{i}-b_{i}\)从小到大排序即可。注意要开\(unsigned~long~long\)
复杂度:\(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define ull unsigned long long
#define ll long long
struct Num
{
	ull a;
	ll delta;
	inline friend bool operator < (Num x,Num y)
		{
			return x.delta<y.delta;
		}
}num[maxn];
int n;
ull s,tp,ans;
int main()
{
	freopen("store.in","r",stdin),freopen("store.out","w",stdout);
	scanf("%d%llu",&n,&s);
	for(int i=1;i<=n;++i)
	{
		scanf("%llu%llu",&num[i].a,&tp);
		num[i].delta=num[i].a-tp;
	}
	sort(num+1,num+n+1);
	for(int i=1;i<=n;++i)
	{
		if(s>=num[i].a)
		{
			if(num[i].delta<=0)
			{
				puts("-1");
				return 0;
			}
			ull tp=(s-num[i].a)/(num[i].delta)+1;
			ans+=tp;
			s-=tp*num[i].delta;
		}
	}
	printf("%llu\n",ans);
	return 0;
}

T2

题意:AB两世界分别有\(n\)个传送门,对于每个A的传送门\((x_{i},y_{i})\),求出符合要求的\(B\)世界传送门的最短距离。若不存在则输出"0"。要求是\(y\)轴坐标差绝对值不大于\(d\),距离为曼哈顿距离。\(n\leq 10^5,d,a_{xi},a_{yi},b_{xi},b_{yi}\leq 10^8\)
题解:把区域拆成左上右上左下右下,把绝对值拆开,坐标变换一下,按x值排序,每种情况都用线段树求坐标和最大值,用自身坐标减去最大值得到最小距离。\(1e8\)的值域需要动态开点。
复杂度:\(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define inf 2e9+5
struct Node
{
	int ls,rs,v;
}t[maxn*100];
struct Coord
{
	int x,y,op;
	friend inline bool operator < (const Coord& a,const Coord& b)
		{
			return a.x==b.x?a.op<b.op:a.x<b.x;
		}
}c[maxn<<1];
int tot,ans[maxn],n,d,cnt;
void modify(int& rt,int l,int r,int pos,int val)
{
	if(!rt) rt=++tot,t[rt].v=-inf;
	t[rt].v=max(t[rt].v,val);
	if(l==r) return;
	int mid=(l+r)>>1;
	if(pos<=mid) modify(t[rt].ls,l,mid,pos,val);
	else modify(t[rt].rs,mid+1,r,pos,val);
}
int query(int rt,int l,int r,int fr,int to)
{
	if(!rt) return -inf;
	if(fr<=l&&to>=r) return t[rt].v;
	int mid=(l+r)>>1,res=-inf;
	if(fr<=mid) res=max(res,query(t[rt].ls,l,mid,fr,to));
	if(to>mid) res=max(res,query(t[rt].rs,mid+1,r,fr,to));
	return res;
}
void solve()
{
	sort(c+1,c+(n<<1)+1);
	int rt=0;
	for(int i=1;i<=(n<<1);++i)
	{
		if(!c[i].op) modify(rt,0,1e8,c[i].y,c[i].x+c[i].y);
		else
		{
			int res=query(rt,0,1e8,c[i].y-d,c[i].y);
			if(res>=0) ans[c[i].op]=min(ans[c[i].op],c[i].x+c[i].y-res);//防止计入-inf,炸int
		}
	}
	for(int i=1;i<=tot;++i)
		t[i].ls=t[i].rs=0;
	tot=0;
}
int main()
{
	freopen("portal.in","r",stdin),freopen("portal.out","w",stdout);
	scanf("%d%d",&n,&d);
	for(int i=1;i<=n;++i) scanf("%d%d",&c[i].x,&c[i].y),c[i].op=i;
	for(int i=n+1;i<=(n<<1);++i) scanf("%d%d",&c[i].x,&c[i].y);
	memset(ans,0x7f,sizeof(ans));
	solve();
	for(int i=1;i<=(n<<1);++i) c[i].y=1e8-c[i].y;
	solve();
	for(int i=1;i<=(n<<1);++i) c[i].x=1e8-c[i].x;
	solve();
	for(int i=1;i<=(n<<1);++i) c[i].y=1e8-c[i].y;
	solve();
	for(int i=1;i<=n;++i) printf("%d\n",ans[i]>inf?0:ans[i]);//注意是大于而不是等于
	return 0;
}

T3

题意:你可以将一个\(01\)序列消去连续一段值相同且长度大于1的序列,并在原位置插入一个值相反(就是异或1)的字符。给定序列,求最少要多少步才能将其变成无法消除。多组数据。\(n\leq 10^4,T\leq 100\)
题解:我们认为长度不为1序列的为2。长度为1则为1。记\(k=\left \lfloor \frac{(1+len)}{2} \right \rfloor\),其中\(len\)为转化后的序列长度。
没有2显然无法消除。
如果2全在k的一侧,则答案显然是从最边上的2往左边推的1的个数(全在右侧就是指最左边往左推,反之同理)。
否则答案为\(max(\left \lfloor \frac{len}{2} \right \rfloor+1,r-l+1)\),其中\(l,r\)为从k往左右两边推的第一个2的位置。
证明:
定义消除为把一个2两边的两项消除。(或者说是把自己消除再连成一个新的)
1.当\(l-1+len-r \leq r-l-1\)也就是中间的比较多。显然每次消至多消除1个中间的,最后两个2也要消两步,所以答案就是\(r-l-1+2=r-l+1\)
2.两侧比较多。一次至多消除两个,所以答案为\(\left \lfloor \frac{len}{2} \right \rfloor+1\)
两个取max即可。
复杂度:\(O(Tn)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
char s[maxn];
int n,m,len,ans,cnt,cnttp;
int a[maxn];
int main()
{
	freopen("eliminate.in","r",stdin),freopen("eliminate.out","w",stdout);
	scanf("%d%d",&n,&m);
	while(m--)
	{
		scanf("%s",s+1);
		cnt=1;
		cnttp=0;
		for(int i=2;i<=strlen(s+1);++i)
		{
			if(s[i]==s[i-1]) ++cnt;
			else
				a[++cnttp]=(cnt==1?1:2),cnt=1;
		}
		a[++cnttp]=(cnt==1?1:2);
		int l=0,r=cnttp+1,k=(1+cnttp)>>1;
		for(int i=k;i;--i)
			if(a[i]==2)
			{
				l=i;
				break;
			}
		for(int i=k;i<=cnttp;++i)
			if(a[i]==2)
			{
				r=i;
				break;
			}
		if(r==cnttp+1&&l==0) ans=0;
		else if(l==0)
		{
			/*for(int i=1;i<=cnttp;++i)
			{
				if(a[i]==1) ++ans;
				else break;
			}
			++ans;	*/
			ans+=r;
		}
		else if(r==cnttp+1)
		{
			/*	for(int i=cnttp;i;--i)
			{
				if(a[i]==1) ++ans;
				else break;
			}
			++ans;	*/
			ans+=cnttp-l+1;
		}
		else ans+=max((cnttp>>1)+1,r-l+1);
	}
	printf("%d\n",ans);
	return 0;
}

11.14 CSP2019前最后一次模拟赛

CSP 2019 rp++

T1

题意:有一颗树,每个节点分别连三条边,编号为\(i\)的节点连向\(2i,2i+1,\frac{i}{2}\)(下取整)(1号节点只连前两条边)。\(n\)组询问,每组询问\(x,y\)间的最短路。\(x,y\leq 10^9,n\leq 10^5\)
题解:
直接判是否在同一层。不是就往上跳到深度相同。然后两个一起往上跳。(和倍增其实差不多)
复杂度:\(O(n\log x)\)

#include<bits/stdc++.h>
using namespace std;
int po[31];
inline void read(int& x)
{
	x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
}

inline void solve(int a,int b)
{
	int depa=upper_bound(po+1,po+31,a)-po;
	int depb=upper_bound(po+1,po+31,b)-po;
	int ans=0;
	//printf("%d %d\n",depa,depb);
	if(depa<depb) swap(a,b),swap(depa,depb);
	while(depa!=depb)
	{
		--depa;
		a>>=1;
		++ans;
	}
	while(a!=b)
	{
		a>>=1;
		b>>=1;
		ans+=2;
	}
	printf("%d\n",ans);
}
int main()
{
	freopen("city.in","r",stdin);
	freopen("city.out","w",stdout);
	for(int i=0;i<=30;++i) po[i]=(1<<i);
	int n,x,y;
	read(n);
	while(n--) read(x),read(y),solve(x,y);
	return 0;
}

T2

题意:有\(n\)张纸,\(c\)种颜色(\(0\sim c-1\)),每次对颜色为\(a\)的纸染色其颜色会变为\(a* b\%c\)
\(k\)次操作,每次将\(l_{i},r_{i}\)中的纸随机染色(所有子集等概率,可以为空),求最后所有纸颜色值之和的期望。\(n,k,c\leq 100\)
题解:对于每张纸分开计算。
\(f[i][j]\)表示染色\(i\)次后颜色为\(j\)的概率。
\(g[i][j]\)表示被区间覆盖\(i\)次中被染色\(j\)次的概率。

\[f[i+1][j*k\%c]+=\frac{f[i][j]}{c} \]

\[g[i][j]=\frac{C_{i}^{j}}{2^i}=\frac{g[i-1][j]+g[i-1][j-1]}{2} \]

\[ans=\sum_{i=1}^{n}{\sum_{j=0}^{cnt[i]}{\sum_{k=0}^{k=c-1}{g[cnt[i]][j]* f[j][k]* k}}} \]

复杂度:\(O(kc^2+nkc)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 105
#define db double
db f[maxn][maxn],g[maxn][maxn],ans;
int cnt[maxn];

int main()
{
	freopen("paint.in","r",stdin),freopen("paint.out","w",stdout);
	int n,c,k,l,r;
	scanf("%d%d%d",&n,&c,&k);
	g[0][0]=1;
	for(int i=0;i<=k;++i)
		for(int j=0;j<=i;++j)
			g[i+1][j]+=g[i][j]/2.0,g[i+1][j+1]+=g[i][j]/2.0;
	f[0][1]=1;
	for(int i=0;i<=k;++i)
		for(int j=0;j<c;++j)
			for(int k=0;k<c;++k)
				f[i+1][j*k%c]+=f[i][j]/c;
	for(int i=1;i<=k;++i)
	{
		scanf("%d%d",&l,&r);
		for(int j=l;j<=r;++j)
			++cnt[j];
	}
	for(int i=1;i<=n;++i)
		for(int j=0;j<=cnt[i];++j)
			for(int k=0;k<c;++k)
				ans+=f[j][k]*g[cnt[i]][j]*k;
	printf("%.3lf\n",ans);
	return 0;
}

T3

题意:给一个\(n\)\(m\)边无向图,从起点\(1\)出发经过所有点到终点\(n\)再经过一开始最先经过的\(\left \lfloor \frac{n-2}{2} \right \rfloor\)个点(次序任意,不计起点)再经过剩余所有点最后回到起点的最短路。\(n\leq 20,m\leq 400\)
题解:
考虑状压。
\(f[i][j][0/1]\)表示当前在第\(i\)点,已经走过的点状态为\(j\),是从起点出发还是从终点出发。
其他看代码吧
复杂度:\(O(n^2*2^n)\)(实际上\(n\leq 18\))

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int dis[22][22],cnt[(1<<18)];
int dp[82][(1<<18)][2];
int main()
{
	freopen("vanilla.in","r",stdin),freopen("vanilla.out","w",stdout);
	int n,m,x,y,z;
	memset(dis,0x3f,sizeof(dis));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
		scanf("%d%d%d",&x,&y,&z),++x,++y,dis[x][y]=dis[y][x]=min(dis[x][y],z);
	for(int i=1;i<=n;++i) dis[i][i]=0;
	for(int i=1;i<=(1<<(n-2));++i)
		for(int x=i;x;x>>=1)
			cnt[i]+=x&1;//状态i中1的个数
	for(int k=1;k<=n;++k)
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	if(n==3)
	{
		printf("%d\n",(dis[1][2]+dis[2][3])<<1);//注意要特判一下
		return 0;
	}
	int n1=(n-2)>>1,n2=n-2-n1;
	for(int l=0;l<=1;++l)
	{
		for(int i=1;i<=n;++i)
			for(int j=0;j<(1<<(n-2));++j)
				dp[i][j][l]=inf;
		if(l) for(int i=2;i<n;++i) dp[i][1<<(i-2)][l]=dis[1][i];
		else for(int i=2;i<n;++i) dp[i][1<<(i-2)][l]=dis[n][i];//初始化
		for(int i=1;i<(1<<(n-2));++i)
			if(cnt[i]<n2)
				for(int j=2;j<n;++j)
					if(dp[j][i][l]<inf)
						for(int k=2;k<n;++k)
							dp[k][i|(1<<(k-2))][l]=min(dp[k][i|(1<<(k-2))][l],dp[j][i][l]+dis[j][k]);//j点,状态i,递推到k点,新状态
	}
	int distp,ans=inf;
	for(int sta=0;sta<(1<<(n-2));++sta)
		if(cnt[sta]==n1)//先遍历的一部分
		{
			distp=inf;
			for(int i=2;i<n;++i)
				if(sta&(1<<(i-2)))
					for(int j=2;j<n;++j)
						if(!(sta&(1<<(j-2))))//i点是前一批最后一个,j点是后一批第一个
							distp=min(distp,dp[i][sta][0]+dis[i][j]+dp[j][(1<<(n-2))-1-sta][1]);//终点前的最短路
			for(int i=2;i<n;++i)//第二次遍历
				if(sta&(1<<(i-2)))
					for(int j=2;j<n;++j)
						if(!(sta&(1<<(j-2))))//同上,变成了从终点开始的dp
							ans=min(ans,distp+dp[i][sta][1]+dis[i][j]+dp[j][(1<<(n-2))-1-sta][0]);
		}
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-11-08 21:59  123789456ye  阅读(6)  评论(0)    收藏  举报