【模拟赛】纪中提高A组 19.8.21 测试

Posted on 2019-08-21 21:57  opethrax  阅读(134)  评论(0编辑  收藏  举报

Task.1 数字

题目大意:定义 \(S(i)\) 表示把 \(1\) ~ \(i\) 的所有数字接起来形成的字符串。给出 \(n\),求包含 \(n\) 字符串的最小的 \(sSi)\)。多组询问

数据范围:\(1\leq N\leq 10^7,T\leq 10^4\)

其实只要确定一个 \(i\) 就能知道 \(n\) 能不能作为 \(S(i)\) 的子串。

\(s(i)\) 表示 \(i\) 本身这个字符串,\(suf(i),pre(i)\) 表示 \(s(i)\) 的后缀( \(suf(i)\) 可以等于 \(s(i)\) ),前缀( \(pre(i)\neq s(i)\) )。

大概有这么几种情况:

\(s(n)\ \Leftarrow\ suf(i-1)+pre(i)\)

\(s(n)\ \Leftarrow\ suf(i-k)+s(i-k+1)+...+s(i-1)+pre(i)\)

\(s(n)\ \Leftarrow\ suf(i-k)+s(i-k+1)+...+s(i-1)+s(i)\)

第一种情况就是把 \(s(n)\) 切开,考虑两个数都不满,见代码中 \(divide()\)

第二种情况就是在某个位置把 \(s(i)\) 的后缀切掉,前面按照完整的做,见代码中 \(middle()\)

第三种就是从最后的某个位置往前扫,扫到头考虑一下前面不满的情况。

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
int len;
ll tn[20],num[20][20],ans;
char s[20];
void init(){
	len=strlen(s+1);
	for(int i=len;i;i--)
		for(int j=i;j;j--)
			num[j][i]=num[j+1][i]+tn[i-j]*(s[j]-'0');
	ans=num[1][len];
}
bool same_suf(ll a,ll b,int k){
	for(int i=0;i<k;i++)
		if(a/tn[i]%10!=b/tn[i]%10) return 0;
	return 1;
}
bool seg_legal(int L,int R){
	ll a,b;
	a=num[L][R];
	for(int i=L-1;i>1;i--){
		if(s[i]=='0')continue;
		b=num[i][L-1];
		if(b+1==a) if(seg_legal(i,L-1)) return 1;
	}
	b=num[1][L-1];
	if(b+1==a) return 1;
	return same_suf(b+1,a,L-1);
}
bool same_pre(ll a,ll b,int l,int k){
	for(int i=l-1;i>=l-k;i--)
		if(a/tn[i]%10!=b/tn[i]%10) return 0;
	return 1;
}
void divide(int L=1,int R=len,int n=len){
	ll a,b;
	for(int i=n-1;i;i--){
		if(s[i+1]=='0')continue;
		a=num[L][i]+1; b=num[i+1][R];
		for(int j=max(i,n-i);j<=17;j++){
			if(n-i<j) b=num[i+1][R]*tn[j-n+i]+a%tn[j-n+i];
			if(same_suf(a,b,i)) ans=min(b,ans);
		}
	}
}
void complete(){
	for(int i=len;i;i--)
		if(s[i]!='0') if(seg_legal(i,len)) ans=min(ans,num[i][len]);
}
void middle(){
	ll a,b;
	for(int i=len;i>1;i--){
		if(s[i]=='0') continue;
		for(int j=i-1;j>1;j--){
			if(s[j]=='0'||i-j<len-i+1) continue;
			a=num[j][i-1]+1;
			b=num[i][len]*tn[i-j-len+i-1];
			if(same_pre(a,b,i-j,len-i+1)) if(seg_legal(j,i-1)) ans=min(a,ans);
		}
	}
}

int main(){
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	tn[0]=1;
	for(int i=1;i<=17;i++) tn[i]=tn[i-1]*10;
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%s",s+1);
		init(); divide();
		complete(); middle();
		printf("%lld\n",ans);
	}
	return 0;
}

Task.2 Maja

题目大意:

数据范围:

如果仔细考虑一下,如果走到某个点按原路返回的话,得到的价值肯定比从这个点出发走另外一条路回起点得到的价值要大。但是只是往返一趟未必用得完所有的步数,肯定要把剩下步数用在收益大的。剩下的步数要么到处走走要么也来回走走。其实只要在两个点见来回跳就是收益最大的(想想为什么)。

定义 \(f_{k,x,y}\) 表示 \(k\) 步走到 \((x,y)\) 的最大收益,每次在更新完这个状态的时候更新答案:在 \((x,y)\) 相邻的格子中去最大的用剩下的步数狂跳。\(k\) 只需要更新到 \(\min(nm,k)\) 就可以了。复杂度 \(O(MN\min(NM,K))\)

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;

template<class T>void read(T &x){
	x=0; char c=getchar();
	while(c<'0'||'9'<c)c=getchar();
	while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef long long ll;
const int N=105;

int n,m,a,b,k;
int c[N][N];
ll f[2][N][N],ans;
int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
void cmax(ll &x,ll y){if(x<y)x=y;}
int main(){
	freopen("maja.in","r",stdin);
	freopen("maja.out","w",stdout);
	read(n); read(m); read(a); read(b); read(k);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)read(c[i][j]);
	int t=min(n*m,k/2); ll mx,tmp;
	bool now=0,pre=1;
	memset(f[0],-1,sizeof(f[0]));
	f[0][a][b]=0;
	for(int i=1;i<=t;i++){
		swap(now,pre);
		memset(f[now],-1,sizeof(f[now]));
		for(int x=1;x<=n;x++)
			for(int y=1;y<=m;y++){
				mx=0;
				for(int j=0;j<4;j++){
					cmax(mx,(ll)c[x+dx[j]][y+dy[j]]);
					if((tmp=f[pre][x+dx[j]][y+dy[j]])<0)continue;
					cmax(f[now][x][y],tmp+c[x][y]);
				}
				if(f[now][x][y]<0)continue;
				mx+=c[x][y];
				cmax(ans,f[now][x][y]*2-c[x][y]+1ll*(k-2*i)/2*mx);
			}
	}
	printf("%lld\n",ans);
	return 0;
}

Task.3

题目大意:

首先你说的这个他们到底是谁???

??????????