第八次ACM训练(Saturday)

A题顺序

13:00 比赛开始
14:13 a dxw
14:32 j yl
14:47 c dxw
15:59 g yl
16:05 d zjl
17:32 h dxw

总结

第八次训练没什么特别大的感触,只意识到自己应该多收集些板子,免得再遇到一些模板题半天下不了手

A - Blank (dp)

description

有n个点,每个点可以放0,1,2,3四种数,现在给出m个限制[l,r,x]要求[l,r]内只有x种数,问方案数(n,m<=100)

solution

本以为是签到题,一个状压走起,打完觉着不太对,怎么样例过不了呀……差点把自己给送了
直接状压是不行的,因为会重复考虑情况
我们设f[i][x][y][z][k]表示前i个数,0-3最后分别位于x,y,z,k位置,但显然有一个数必须是i,所以去掉一维,同时我们考虑剩下的数的位置从大到小排即(x>y>z),那么暴力枚举更新即可

code

#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rp(i,a,b) for(int i=a;i>=b;i--)
using namespace std; 
const int maxn=1e2+5;
const ll mo=998244353;
struct code{
	int l,r,x,y;
}a[maxn];
int n,m,i,t,j,k,l,x,y,z,p,T,r,mid,q,num;
ll f[2][maxn][maxn][maxn],ans; 
bool cmp(code x,code y){
	return x.r<y.r;
}
void make(int i,int x,int y,int z){
	f[1-p][x][y][z]%=mo;
	f[p][i][y][z]+=f[1-p][x][y][z];
	f[p][i][x][z]+=f[1-p][x][y][z];
	f[p][i][x][y]+=f[1-p][x][y][z];
	f[p][x][y][z]+=f[1-p][x][y][z];
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	scanf("%d",&T);q=1;
	while (T--){
		scanf("%d%d",&n,&m);
		fo(i,1,m)scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].x);
		sort(a+1,a+m+1,cmp); 
		fo(j,0,3)f[0][0][0][0]=1;
		p=0;num=1;a[m+1].r=n+1;ans=0;
		fo(i,0,n){
			p=1-p;
			fo(x,0,max(i,1)-1)
				fo(y,0,max(x,1)-1)
					fo(z,0,max(y,1)-1){
						if (!f[1-p][x][y][z]) continue;
						t=0;
						for (l=num;a[l].r==i && !t && l<=m;l++){
							q=1;
							if (x>=a[l].l) q++;
							if (y>=a[l].l) q++;
							if (z>=a[l].l) q++;
							if (q!=a[l].x) t=1;
						}
						if (!t){
							if (i==n)ans+=f[1-p][x][y][z]%mo;
							else make(i,x,y,z);	
						}
						f[1-p][x][y][z]=0;
					} 
			while (a[num].r==i && num<=m) num++;
		}
		printf("%lld\n",ans%mo);
	} 
}

C - Vacation (结论题?)

description

给你0-n辆车的长度li、车头距离终点si、最大速度vi,保证si≥si+1+li+1,每辆车在遇到前面有车之前一直会已最大速度行驶,问0号车车头到达终点的时间

solution

以后拷上一题的初始化一定要去LL和重新设置数据范围
假如只有一辆车,时间是s0/v0
假如有两辆车,有两种情况:1、自己奇慢s0/v0 2、前面的大兄弟奇慢,自己追上后还得以它的龟速走,过线后再过一个它的车长,无论自己跑多快,最后还是得以前一辆车的节奏来(s1+l1)/v1
……(你是否发现了什么?)
我们取max((sj+i=0jlj)/vj)max((sj+\sum_{i=0}^{j}lj)/vj)即可

code

#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rp(i,a,b) for(int i=a;i>=b;i--)
using namespace std; 
const int maxn=1e6+5;
const ll mo=998244353;
struct code{
	int s,v,l;
}a[maxn];
int n,m,i,t,j,k,l,p,T,r,mid,q,num;
double x,y,z;
int read(){
	int f=1,s=0;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
	for(;c>='0'&&c<='9';c=getchar())s=s*10+c-'0';
	return f*s;
}
bool cmp(code x,code y){
	return x.s<y.s;
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	while (scanf("%d",&n)!=EOF){
		fo(i,0,n)a[i].l=read();
		fo(i,0,n)a[i].s=read();
		fo(i,0,n)a[i].v=read();
		double t=a[0].s*1.0/a[0].v,sum=0;
		fo(i,1,n){
			sum+=a[i].l;
			t=max(t,(sum+a[i].s)*1.0/a[i].v);
		}
		printf("%.10lf\n",t);
	}
}

D - Path (spfa+dinic)

description

给定一个有向图,问怎样删除一些边,使得最后1-n的最短路变大,且删除边的总和尽量小

solution

构建出一个最短路的DAG,跑一遍最小割

code

#include<bits/stdc++.h>
#define ll long long
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define rp(t,a) for(ll t=first[a];t;t=nex[t])
using namespace std;
const ll maxn=2e4+5;
ll first[maxn],last[maxn],nex[maxn],value[maxn];
ll v[maxn],bz[maxn],dui[maxn],d[maxn];
ll n,m,i,t,j,k,l,x,y,z,num,T,ans;
void lian(ll x,ll y,ll z){
	last[++num]=y;nex[num]=first[x];first[x]=num;value[num]=z;
}
void spfa(ll s){
	memset(d,127,sizeof(d));d[s]=0;
	ll i=0,j=1;v[j]=s;bz[s]=1;
	while(i<j){
		x=v[++i];
		rp(t,x){
			if (d[last[t]]<=d[x]+value[t])continue;
			d[last[t]]=d[x]+value[t];
			if (!bz[last[t]]) bz[v[++j]=last[t]]=1;
		}
		bz[x]=0;
	}
}
ll bfs(){
	memset(d,0,sizeof(d));d[1]=1;
	ll i=0,j=1;v[j]=1;
	while(i<j){
		x=v[++i];
		rp(t,x)
			if (value[t] && !d[last[t]]) d[v[++j]=last[t]]=d[x]+1;
	}
	return d[n];
}
ll dg(ll x,ll sum){
	if (x==n) return sum;
	ll p=sum,k;
	rp(t,x){
		if (!value[t]||d[last[t]]!=d[x]+1)continue;
		k=dg(last[t],min(p,value[t]));
		if (k){
			value[t]-=k;
			value[dui[t]]+=k;
			p-=k;
			if (!p) break;
		}
	}
	if (p==sum) d[x]=-1;
	return sum-p;
}
int main(){
	//freopen("data.in","r",stdin);
	scanf("%lld",&T);
	while (T--){
		scanf("%lld%lld",&n,&m);
		num=0;
		memset(first,0,sizeof(first));
		fo(i,1,m)scanf("%lld%lld%lld",&x,&y,&z),lian(x,y,z);
		spfa(1);
		//make DAG
		fo(i,1,n)
			rp(t,i)
				if (d[i]+value[t]!=d[last[t]]) value[t]=0;
				else lian(last[t],i,0),dui[num]=t,dui[t]=num;
		//SAP
		ans=0;
		while (bfs())ans+=dg(1,2e14);
		printf("%lld\n",ans);
	}
} 

G - String(贪心)

description
给定一个仅包含小写字母的字符串
从中选取出一个长度为k的子序列
输出字典序最小的子序列
不过子序列做出一定的限制:每个字母至少出现L[i]次至多出现R[i]次
solution
很显然我们可以贪心地想 每个位置选择符合条件的最小的字母
于是乎 问题的重点在于判断 这个位置填这个字母合不合适

经过了一段时间的思考(和提交的WA)可以发现有几种情况是不合适的:

  • 所有字母全部填上限次也填不满剩余的空位
  • 所有字母全部填下线次也超过剩余的空位
  • 这个字母在这个位置之后没有了
  • 这个字母已经填过R次了
  • 如果这个位置填了这个字母,剩余的其他字母就算都填下限次也会超过剩余的空位

emmm可能有一些条件是多余的
不过并没有进行尝试
所以还是全部列出来

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>

typedef long long LL;
const int oo = 0x3f3f3f3f;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
using namespace std;
int read() {
	int f = 1, s = 0; char c = getchar();
	for (; c < '0' || c>'9'; c = getchar())if (c == '-')f = -1;
	for (; c >= '0' && c <= '9'; c = getchar())s = s * 10 + c - '0';
	return f * s;
}
char s[N];
int f[N][26], ans_[N];
int l[26], r[26];
queue<int>q[26];
bool check(int x, int left) {
	if (q[x].empty())return false;
	if (r[x] == 0)return false;
	int most = 0, least = 0;
	for (int i = 0; i < 26; i++)
		if (f[q[x].front()][i] < l[i])return false;
		else {
			most += min(r[i], f[q[x].front()][i]);
			least += l[i];
		}
	if (most < left)return false;
	if (least > left)return false;
	if (left - 1 < least - l[x])return false;
	return true;
}
void work() {
	int n = strlen(s + 1);
	for (int i = 0; i < 26; i++)f[n + 1][i] = 0;
	for (int i = n; i >= 1; i--) {
		for (int j = 0; j < 26; j++)
			f[i][j] = f[i + 1][j];
		f[i][s[i] - 'a']++;
	}
	int k = read();

	for (int i = 0; i < 26; i++) {
		l[i] = read(); r[i] = read();
		while (q[i].empty() == false)q[i].pop();
	}
	for (int i = 1; i <= n; i++)q[s[i] - 'a'].push(i);
//printf("\n");
	int ans;
	for (ans = 0; ans < k; ans++) {
		bool flag = true;
		for (int i = 0; i < 26; i++)
			if (check(i, k - ans ) == true) {
				ans_[ans] = q[i].front();
				q[i].pop();
				if (l[i])l[i]--;
				if(r[i])r[i]--;
				flag = false;
				break;
			}
	//	printf("%d %d\n", ans, ans_[ans]);
		if (flag)break;
		for (int i = 0; i < 26; i++)
			while (q[i].empty() == false && q[i].front() < ans_[ans])
				q[i].pop();
	}
	if (ans < k - 1)printf("-1");
	else for (int i = 0; i < k; i++)putchar(s[ans_[i]]);
	printf("\n");
}
int main() {
	while (scanf("%s", s + 1) != EOF)work();
	return 0;
}

H - Kingdom (记忆化搜索+?)

description

给你一棵树的残缺的前序遍历和中序遍历(缺的数用0表示),问有多少种树的前序遍历和中序遍历满足条件

solution

前序遍历好啊,直接告诉你根是什么,所以我们主要根据前序遍历来搜索,设前序遍历a[i],中序遍历b[i]
设状态f(l,r,x,y)表示当前是前序遍历[l,r]对应中序遍历的[x,y],我们对a[l]的情况分类讨论:

  • 假如现在a[l]存在且[x,y]中存在a[l](设在b中位置p),那我们直接就分成f(l+1,l+p-x,x,p-1)和f(l+p-x+1,r,p+1,y)
  • 假如现在a[l]存在但[x,y]不存在与之对应的数,那我们就考虑在[x,y]中找一个满足遍历要求的0和他配对
  • 假如a[l]不存在,那在[x,y]中随便一个满足遍历要求且在[l,r]无配对的点都可以和他配对

还有一个问题,0和0的配对的方案数,在统计在两个遍历[l,r][x,y]中都没有出现过的数的个数f[l][r][x],当按照(l+1,l+p-x,x,p-1)的方式分割时,相当于把f[l][r][x]个数选出f[l+1][l+p-x][x]个出来放到左子树,因此要乘方案数Cf[l][r][x]f[l+1][l+px][x]C_{f[l][r][x]}^{f[l+1][l+p-x][x]},其他同理。
由于一个区间算过一次就不用再算了,因此我们可以记忆化一下,设为g[l][r][x]。

code

#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define rp(i,a,b) for(ll i=a;i>=b;i--)
using namespace std; 
const ll maxn=1e2+5;
const ll mo=998244353;
ll a[maxn],b[maxn],c[maxn],d[maxn],bz[maxn],f[maxn][maxn][maxn];
ll c1[maxn][maxn],g[maxn][maxn][maxn];
ll n,m,i,t,j,k,l,p,T,r,mid,q,num;
ll dg(ll l,ll r,ll x,ll y){
	if (r<=l) return 1;
	if (g[l][r][x]>=0) return g[l][r][x];
	ll t,k,k1,num;t=k=k1=0;
	memset(bz,0,sizeof(bz));num=f[l][r][x];
	if (c[a[l]]){
		fo(i,x,y){
			if (k1<i && k<=i-x+l && i==c[a[l]])t=c1[num][f[l+1][i-x+l][x]]*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
			k=max(k,d[b[i]]);
			k1=max(k1,c[a[l+1+i-x]]);
		}
	}else if (a[l]){
		fo(i,x,y){
			if (k1<i && k<=i-x+l && !b[i])t+=c1[num][f[l+1][i-x+l][x]]*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
			k=max(k,d[b[i]]);
			k1=max(k1,c[a[l+1+i-x]]);
		}
	}else{
		fo(i,x,y){
			if (k1<i && k<=i-x+l){
				if (b[i]){
					if (!d[b[i]])t+=c1[num][f[l+1][i-x+l][x]]*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
				}else t+=num*c1[num-1][f[l+1][i-x+l][x]]%mo*dg(l+1,i-x+l,x,i-1)%mo*dg(i-x+l+1,r,i+1,y)%mo;
			}
			k=max(k,d[b[i]]);
			k1=max(k1,c[a[l+1+i-x]]);
		}
	}
	return g[l][r][x]=t%mo;
}
int main(){
	//freopen("data.in","r",stdin);
	//freopen("data.out","w",stdout);
	scanf("%lld",&T);
	c1[0][0]=1;
	fo(i,1,100){
		c1[i][0]=1;
		fo(j,1,i) c1[i][j]=(c1[i-1][j-1]+c1[i-1][j])%mo;
	}
	while (T--){
		scanf("%lld",&n);
		memset(c,0,sizeof(c));
		memset(d,0,sizeof(d));
		fo(i,1,n)scanf("%lld",&a[i]),d[a[i]]=i;
		fo(i,1,n)scanf("%lld",&b[i]),c[b[i]]=i;
		fo(i,1,n)
			fo(j,1,n){
				memset(bz,0,sizeof(bz));num=k=0;
				while (i+k<=n && j+k<=n){
					if (!bz[a[i+k]] && a[i+k])bz[a[i+k]]=1,num++;
					if (!bz[b[j+k]] && b[j+k])bz[b[j+k]]=1,num++;
					f[i][i+k][j]=k+1-num;
					g[i][i+k][j]=-1;
					k++;
				}
			}
		c[0]=d[0]=0;
		ll ans;
		ans=dg(1,n,1,n);
		printf("%lld\n",ans);
	}
}

HDU - 6590 Code (模拟?

description
(题目套了个AI的背景hhh,不过维数只有2)
y=sign(x1w1+x2w2+b)y=sign(x1*w1+x2*w2+b)
sign(t)={1t>00t=01t<0 sign(t)=\begin{cases} 1 & t>0 \\ 0 & t=0 \\ -1 & t<0 \end{cases}
给定x1,x2,yx1,x2,y(PPS:题目给的y只会是1或者-1)
问是否存在w1,w2,bw1,w2,b满足所有的“样本”

solution
(最开始没看到这个sign。。写了一个高斯消元着实丢人
因为题目给的y是1或者-1
我们就可以动点脑筋
假如有yi=1yj=1y_i=1 并且 y_j=-1我们就可以推导出
sign(x1iw1+x2iw2+b)>sign(x1jw1+x2jw2+b) sign(x1_i*w1+x2_i*w2+b)>sign(x1_j*w1+x2_j*w2+b)
x1iw1+x2iw2+b>x1jw1+x2jw2+bx1_i*w1+x2_i*w2+b>x1_j*w1+x2_j*w2+b
x1iw1+x2iw2>x1jw1+x2jw2x1_i*w1+x2_i*w2>x1_j*w1+x2_j*w2
x1iw1x1jw1>x2jw2x2iw2x1_i*w1-x1_j*w1>x2_j*w2-x2_i*w2
(x1ix1j)w1>(x2jx2i)w2(x1_i-x1_j)*w1>(x2_j-x2_i)*w2
这个式子应该永远成立

然而两个变量不好处理于是乎根据高中数学导数题的常用套路,左右两边同时除以w1(记得特殊考虑w1=0的情况!)

然后就是解不等式组 看看有没有解喽
(有人可能会觉得只有一个方向肯定有解啊?)
(一定要小心处理乘除负数导致不等号方向改变的情况!)

code

#include<bits/stdc++.h>
typedef long long LL; 
const int oo=0x3f3f3f3f; 
const int N=110; 
const int MOD=1e9+7;
using namespace std;
int read(){
	int f=1,s=0;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
	for(;c>='0'&&c<='9';c=getchar())s=s*10+c-'0';
	return f*s;
}

int x1[N],x2[N],y[N];
bool work(){
	int n=read();
	for(int i=0;i<n;i++){
		x1[i]=read();
		x2[i]=read();
		y[i]=read();
	}
	//w1>0
	double L=-1LL<<31,R=1LL<<31;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i==j)continue;
			if(y[i]==1&&y[j]==-1){
				int fenmu=x2[j]-x2[i];
				int fenzi=x1[i]-x1[j];
				if(fenmu==0)continue;
				if(fenmu<0)L=max(L,1.0*fenzi/fenmu);
				else R=min(R,1.0*fenzi/fenmu);
			}
		}
//	printf("%.3lf %.3lf\n",L,R);
	if(L<=R)return true;
	
	
		
	//w1==0	
	L=-1LL<<31,R=1LL<<31;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i==j)continue;
			if(y[i]==1&&y[j]==-1){
				int fenmu=x2[j]-x2[i];
				int fenzi=0;
				if(fenmu==0)continue;
				if(fenmu>0)L=max(L,1.0);
				else R=min(R,-1.0);
			}
		}
	//printf("%.3lf %.3lf\n",L,R);	
	if(L<=R)return true;
	
	//w1<0
	L=-1LL<<31,R=1LL<<31;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i==j)continue;
			if(y[i]==1&&y[j]==-1){
				int fenmu=x2[j]-x2[i];
				int fenzi=x1[i]-x1[j];
				if(fenmu==0)continue;
				if(fenmu>0)L=max(L,1.0*fenzi/fenmu);
				else R=min(R,1.0*fenzi/fenmu);
			}
		}
//	printf("%.3lf %.3lf\n",L,R);
	if(L<=R)return true;
	
	return false;
	
} 


int main(){
	int T=read();
	for(int i=1;i<=T;i++){
		if(work()==true)printf("Successful!\n");
		else printf("Infinite loop!\n");
	}
	return 0;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章