2022.10.6测试

一测:\(290pts\),注意判 \(-1\),因此丢了 \(40pts\)

T1:预估橙(1000)

T2:预估绿(1600)

T3:P3942 将军令(蓝)

T4:AT655 玉座の間(黑)

两道最简单的搞了 \(130pts\),难的两道还搞了 \(160pts\),太蒻了。

炼心(reheart.cpp/.c)
【题目背景】
Cafeiin神情认真,专心的听着老师讲话。
“既然你已拜入算法门下,我便为你讲讲这算法的修炼之道,
算法之道,分几重境界,初入编程,便是普及,普及之上更有提高。提高之上,一劫一境界。提高
巅峰强者引天雷入体渡劫,便能成就省选,省选再渡劫,便是集训队,集训队再渡劫,便证得IOI大道。
天榜之下还有地榜,从校赛市赛省赛一路渡劫,证得ICPC world final总决赛。
不过,天榜学历皆不过高中,个个都是万里挑一的奇才。
为师便考考你,这天地二榜,谁为首。”
【题目描述】
算法之路中有 名同学 第 个同学有 ,表示该名同学的程序设计能力, 表示该名同学的学历(
表示幼儿园, 表示小学, 表示中学,大于 表示更高学历),它的名字是 为字符串
天榜学历不超过 ,地榜学历不小于 ,满足学历要求的同学会分别在两个榜单中排名.
你需要分别输出,天榜地榜当中,程序设计能力最强的同学中,学历最小的名字,存在多个则输出
字典序最小的那一个,如果榜上无人,输出
【输入格式】
第一行一个整数
接下来 行,每行有三个数据,分别是名字字符串 ,程序设计能力 ,学历
【输出格式】
两行,第一行为天榜强者的名字,第二行为地榜强者的名字
【样例】
【样例输入】
【样例输出】
【数据范围与提示】
对于 的数据,天榜强者为
对于 的数据,地榜强者为
对于 的数据,
对于 100% 的数据,跟你想象的一样水。 
保证名字互不相同

T1:

不会有人这题都拿不满吧。

没错就是我。

注意判 \(-1\),就没了。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int n,cnp,cnt;
struct node
{
	char s[20];
	int dat1,dat2;
}a[N],b[N];
int cmp(node x,node y)
{
	if(x.dat1==y.dat1)
	{
		if(x.dat2==y.dat2)
		{
			int len1=strlen(x.s+1),len2=strlen(y.s+1);
			for(int i=1;i<=min(len1,len2);i++)
			{
				if(x.s[i]!=y.s[i])return x.s[i]<y.s[i];
			}
			return len1<len2;
		}
		return x.dat2<y.dat2;
	}
	return x.dat1>y.dat1;
}
int main()
{
	freopen("reheart.in","r",stdin);
	freopen("reheart.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		char ch[20];
		int x,y;
		scanf("%s",ch+1);
		scanf("%d%d",&x,&y);
		int len=strlen(ch+1);
		if(y<=13)
		{
			a[++cnt].dat1=x,a[cnt].dat2=y;
			for(int j=1;j<=len;j++)a[cnt].s[j]=ch[j];
		}
		else 
		{
			b[++cnp].dat1=x,b[cnp].dat2=y;
			for(int j=1;j<=len;j++)b[cnp].s[j]=ch[j];
		}
	}
	sort(a+1,a+1+cnt,cmp);
	sort(b+1,b+1+cnp,cmp);
	if(cnt)printf("%s\n",a[1].s+1);
   else printf("-1\n");
	if(cnp)printf("%s",b[1].s+1);
   else printf("-1");
	return 0;
}

炼气(rebreth.cpp/.c)
【题目背景】
师傅讲完算法大道,便将一本黑色厚书丢给了Cafeiin,让Cafeiin好好阅读,随后又开始聊起了当年
往事:
“想当年,为师也是天榜榜上有名的天骄,年纪轻轻便入半步省选,只可惜为了宗门强行渡劫损了元
神......”
虽然Cafeiin知道师傅是在吹牛,但Cafeiin还是看起了师傅给自己的书。
【题目描述】
这本书可以看作一个长度为 的字符串 ,这个字符串由小写字母组成。
师傅说,这本书有千万种变化,如果对于一个区间 ,如果字符串
满足,有不超过 种字符出现了奇数次,则这个区间是一个“法门”。
( )
Cafeiin想知道这本书总共有多少个“法门”
5
cafeiin 18 11
George_Plover 20 12
Karshilov 19 12
wzk 23 14
Lenska 18 12
George_Plover
wzk
【输入格式】
第一行一个整数 ,表示字符串 的长度
接下来一行,一个长度为 的字符串
【输出格式】
一行一个整数,表示字符串 中的法门数量
【样例】
【样例输入】
【样例输出】
样例解释:区间(1,1),(1,3),(1,4),(2,2),(2,4),(3,3),(4,4)满足条件
【数据范围与提示】
对于 的数据,
对于 的数据,
对于 100% 的数据,n<=10^6 ,字符串 只由小写字母组成

T2:

对每个字母求前缀和,用 \(0,1\) 状态存储前缀和是否为奇数。

那么可以得到 \(26\) 进制的状压。

枚举 \(r\) 端点,可以得到 \(1\)\(r\)\(26\) 个字母的状态,枚举目标使哪一位上的数为 \(1\),找到需要异或的数,在桶里寻找之前是否有状态符合,累加即可。

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int N=1e6+5;
int n,res,a[1<<26];
char s[N];
signed main()
{
	//freopen("rebreth.in","r",stdin);
	//freopen("rebreth.out","w",stdout);
	scanf("%lld",&n);
	scanf("%s",s+1);
	int ans=0,num=0;
	a[0]++;
	for(int i=1;i<=n;i++)
	{
		num^=(1<<(s[i]-'a'));
		for(int j=0;j<26;j++)ans+=a[num^(1<<j)];
		ans+=a[num];
		a[num]++;
	}
	printf("%lld",ans);
	return 0;
}

T3:

树形结构,考虑树形 dp。

\(f_{i,j}\) 为以 \(i\) 的子树全部被覆盖,且在 \(i\) 以上的 \(j\) 个祖先被覆盖。
\(g_{i,j}\) 为以 \(i\) 为子树的第 \(j\) 层及以下的全部被覆盖,第 \(j-1\) 层以上的未被覆盖。

方程初始值,对 \(f_{i,j}\) 赋为 \(1\)\(g_{i,0}\) 赋为 \(1\),其余赋为 \(0\)

\(son\)\(i\) 的儿子,方程转移:

\(f_{i,j}=\min(f_{i,j}+g_{son,j},f_{son,j+1}+g_{i,j+1})\)

\(g_{i,0}=f_{i,0}\)

\(g_{i,j}=g_{i,j}+g_{son,j-1}\)

\(f\) 数组求后缀最小值,对数组 \(g\) 求前缀最小值即可。

详细解释:

对于第一个方程,一个点要么以自己转过来,要么用自己的儿子转过来,自己转过来就用自己转的方案加上自己儿子 \(j\) 层下全部被覆盖的方案,所以为 \(g_{son,j}\),若由儿子转移过来,需要将其他儿子覆盖到,所以需要 \(f_{son,j+1}\),还要将自己 \(j+1\) 层以下全部覆盖,所以为 \(g_{i,j+1}\)

对于第二个方程,\(g_{i,0}\) 实际上表示的是以 \(i\) 为子节点的树全部被覆盖,所以与 \(f_{i,0}\) 等价。

对于第三个方程,若第 \(j\) 层及以下的全部被覆盖,所以是自己儿子的第 \(j-1\) 层及以下全部被覆盖。

对于求后缀的最小值,如果一个方法比你往上覆盖更多,还比你更优,那么肯定选那个方法,求前缀最小值同理。

转移即可。

我也不知道我考场怎么想出来的。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=1e5+5;
const int K=25;
int n,k,f[N][K],g[N][K],t;
//f[i][j] 表示 i 以下全部打通且往上打了 j 位
//g[i][j] 表示 i 以下的 j 层全部打通,j 以上的未打通 
vector<int>a[N];
void dfs(int x,int fa)
{
	for(int i=0;i<=k;i++)f[x][i]=1;
	g[x][0]=1;
	f[x][k+1]=1e9;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa)continue;
		dfs(a[x][i],x);
		for(int j=0;j<=k;j++)f[x][j]=min(f[x][j]+g[a[x][i]][j],f[a[x][i]][j+1]+g[x][j+1]);
		for(int j=k-1;j>=0;j--)f[x][j]=min(f[x][j],f[x][j+1]);
		g[x][0]=f[x][0];
		for(int j=1;j<=k;j++)g[x][j]+=g[a[x][i]][j-1];
		for(int j=1;j<=k;j++)g[x][j]=min(g[x][j],g[x][j-1]);
	}
}
int main()
{
	//freopen("rebody.in","r",stdin);
	//freopen("rebody.out","w",stdout);
	scanf("%d%d",&n,&k);
	scanf("%d",&t);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		a[u].push_back(v);
		a[v].push_back(u);
	}
	dfs(1,0);
	printf("%d",f[1][0]);
	return 0;
}

T4:

相信看题解的大家应该不怎么会费用流。

首先我们要学习一下费用流。

费用流是什么呢,可以理解为边带权值的网络流。

那么最小费用最大流,是指在满足最大流的情况下的最小费用。

那么我们就要实现这个过程。

首先对于一条有向边,建立的反向边的权值为原权值的相反数。

所以明显图带负数,只能用 SPFA,泪目

每一次跑一次 SPFA 找到最短路,当然计算最短路时没有流量的边是不能走的,期间记录流量。

如果发现汇点最短路发生了更新,那么可以从汇点出发,往回跑最短路,然后就是减去流量,对反向边增加流量的操作了。

往回跑最短路可以由每次更新一个点的最短路值时记录从哪一个点转移过来的即可。

本题也是一个费用流。

首先可以保证,在 \(y\) 轴同侧的两点不会出现一个点跑到另一个坐标轴与另外一点进行匹配的情况,这个应该很好证。

所以对于坐标轴两边的点,可能都是能匹配的,那么就可以连边,而对于每一个点,也可以连 \(y\) 轴,所以也与 \(y\) 轴连边。

综上,我们可以考虑建出这个图。

首先,将每个点复制成两份,从 \(1\)\(n\),与从 \(n+1\)\(2\times n\)

第二步,将源点 \(S\)\(1\)\(n\) 连边,流量为 \(1\),边权为 \(0\)

第三步,走 \(y\) 轴,可以理解为自己与自己匹配,连接 \(i\)\(i+n\),流量为 \(1\),边权为 \(\left|x_i\right|\)

第四步,连接坐标轴两边的点,当 \(x_i\times x_j<0\) 时,说明在坐标轴两端,连接 \(i\)\(j+n\),流量为 \(1\),边权为 \(\sqrt{(x_i+x_j)^2+(y_i-y_j)^2}\div 2\)。因为如果要匹配会匹配两次,所以要每次除以 \(2\),而连向 \(y\) 轴只有一次,所以不需要除以 \(2\)

第五步,连接 \(n+1\)\(2\times n\) 与汇点 \(T\),流量为 \(1\),边权为 \(0\)

最后跑最小费用最大流即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<deque>
#include<vector>
using namespace std;
const int N=205;
int n;
inline double tabs(int x)
{
	return x>0?x:-x;
}
struct node2
{
	int x,y;
}t[N];
struct node
{
	int to,v;
	double w;
};
vector<node>a[N];
vector<int>b[N];
deque<int>q;
double dis[N],ans;
bool vis[N];
int incf[N],pre[N];
bool spfa()
{
	for(int i=1;i<=2*n+3;i++)dis[i]=1e9;
	memset(vis,0,sizeof(vis));
	q.push_back(2*n+1);
	dis[2*n+1]=0;
	incf[2*n+1]=1e9;
	while(!q.empty())
	{
		int x=q.front();
		q.pop_front();
		vis[x]=0;
		int len=a[x].size();
		for(int i=0;i<len;i++)
		{
			if(!a[x][i].v)continue;
			if(dis[x]+a[x][i].w<dis[a[x][i].to])
			{
				dis[a[x][i].to]=dis[x]+a[x][i].w;
				incf[a[x][i].to]=min(incf[x],a[x][i].v);
				pre[a[x][i].to]=b[x][i];
				if(!vis[a[x][i].to])
				{
					if(!q.empty()&&dis[a[x][i].to]<dis[q.front()])q.push_front(a[x][i].to);
					else q.push_back(a[x][i].to);
					vis[a[x][i].to]=0;
				}
			}
		}
	}
	//cout<<dis[2*n+3]<<" "<<incf[2*n+3]<<"afsda\n";
	if(dis[2*n+3]<1e9-1e-9)return true;
	return false;
}
void dinic()
{
	while(spfa())
	{
		int x=2*n+3;
		ans+=dis[2*n+3]*incf[2*n+3];
		while(x!=2*n+1)
		{
			int t=pre[x];
			int p=a[x][t].to;
			a[x][t].v+=incf[2*n+3];
			a[p][b[x][t]].v-=incf[2*n+3];
			x=p;
		}
	}
}
int main()
{
	//freopen("aichemy.in","r",stdin);
	//freopen("aichemy.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&t[i].x,&t[i].y);
	for(int i=1;i<=n;i++)
	{
		a[2*n+1].push_back({i,1,0});
		a[i].push_back({2*n+1,0,0});
		b[2*n+1].push_back(a[i].size()-1);
		b[i].push_back(a[2*n+1].size()-1);
		for(int j=1;j<=n;j++)
		{
			if(t[j].x*t[i].x<0)
			{
				a[i].push_back({j+n,1,sqrt((t[i].x+t[j].x)*(t[i].x+t[j].x)+(t[i].y-t[j].y)*(t[i].y-t[j].y))/2});
				a[j+n].push_back({i,0,-sqrt((t[i].x+t[j].x)*(t[i].x+t[j].x)+(t[i].y-t[j].y)*(t[i].y-t[j].y))/2});
				b[i].push_back(a[j+n].size()-1);
				b[j+n].push_back(a[i].size()-1);
				//cout<<i<<" "<<j<<" "<<sqrt((t[i].x+t[j].x)*(t[i].x+t[j].x)+(t[i].y-t[j].y)*(t[i].y-t[j].y))<<endl;
			}
		}
		a[i+n].push_back({2*n+3,1,0});
		a[2*n+3].push_back({i+n,0,0});
		b[i+n].push_back(a[2*n+3].size()-1);
		b[2*n+3].push_back(a[i+n].size()-1);
		a[i].push_back({i+n,1,tabs(t[i].x)});
		a[i+n].push_back({i,0,-tabs(t[i].x)});
		b[i].push_back(a[i+n].size()-1);
		b[i+n].push_back(a[i].size()-1);
	}
	dinic();
	printf("%.7lf",ans);
	return 0;
}
/*
3
1 2
-1 3
-2 0
*/
posted @ 2023-02-24 13:57  Gmt丶Fu9ture  阅读(31)  评论(0)    收藏  举报