栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

2021icpc上海补题E,D,G,I,H,K

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

2021icpc上海补题E,D,G,I,H,K

目录

E Strange Integers:

D Strange Fractions:

G Edge Groups

I Steadily Growing Steam:

H Life is a Game:

K Circle of Life:


E Strange Integers:

签到题

题意为给定一个数组和一个数k

问能找出几对绝对值相差超过k的数

考虑直接排序二分,然后每次更新数即可

const int N=1e6+10;
int a[N];
signed main(){
    int n,k;
    cin>>n>>k;
    fer(i,1,n) scanf("%lld",&a[i]);
    sort(a+1,a+n+1);
    int ans=0;
    int t=a[1];
    int it=1;
    while(it!=n+1)
    {
        t=a[it];
        t+=k;
        ans++;
        it=lower_bound(a+1,a+1+n,t)-a;
    }
    cout<

D Strange Fractions:

题意为给定p ,q 求得一对a,b使得p/q=a/b+b/a

一道解方程题,只需要设x=a/b,则b/a=1/x,然后设k=p/q,

原式等于 ,只需要导出已知k求x的解即可

直接求根公式

当判别式<0,或判别式开跟不是整数时就是无解,即当

大于等于0时,且为整数时

有

为了保证整数,我们取a等于,b=2*q即可

代码如下

signed main()
{
    int t = 1;
    scanf("%lld",&t);
    while(t--)
    {
        bool bol=true;
        int p,q;
        scanf("%lld%lld",&p,&q);
        int k=p*p-4*q*q;
        int kk=sqrt(k);
        if(kk*kk==k)
            bol=true;
        else
            bol=false;
        int gcdd=__gcd(p-kk,q*2);
        if(!bol)
            printf("0 0n");
        else
        {
            printf("%lld %lldn",(p-kk)/gcdd,q*2/gcdd);
        }
    }
    return 0;
}

G Edge Groups

给定一棵树,让我们把边分成(n-1)/2组,使得每组中有且仅有两条边,而且两条边公用同一个顶点,求分成的组个数,答案对998244353取模

首先我们可以一步一步想,如果我们知道有x条边,让我们把他分成两两一组,有几种方法?

这个答案应该是不难的,答案应该是

我们把组合数乘开,上面是

下面是

我们把上下分母约去,上面分子约去奇数项,可以得到答案即为

接下来我们只需要思考如何找到每个节点可以用几条边,这样就可以树形dp

首先我们假设对于节点f,他有两种不同的儿子u和v

儿子u有两个儿子,儿子v有三个儿子
 

那么对于f-u这条边1,他对f是可用的,因为儿子u可以内部消化完全不需要这条边

反之对于儿子v,由于他的子树有奇数条边,所以必须要加上f-v这条边才能分配

所以对于f,f-v这条边就是不可用的

问题即迎刃而解

代码如下:

const int N=1e6+10;
const int mod=998244353;
vector v[N];
int f[N];
bool dfs(int son,int fa){
    f[son]=1;
    int usedg=0;
    for(auto t:v[son]){
        if(t==fa) continue;
        if(!dfs(t,son)){
            usedg++;
        }
        f[son]=f[son]*f[t]%mod;
 
    }
    for(int i=1;i<=usedg;i+=2){
        f[son]=(f[son]*i)%mod;
    }
    return usedg&1;
}
signed main()
{
    int n;
    cin>>n;
    int a,b;
    fer(i,1,n-1){
        scanf("%lld%lld",&a,&b);
        v[a].pb(b);
        v[b].pb(a);
    }
    dfs(1,0);
    cout< 

I Steadily Growing Steam:

有n张牌,每张牌有一个价值w和点数t,先选择至多m张牌将其点数 t 翻倍,再从n张牌中选若干张牌分成两堆,要求两堆内点数之和相等,求选出的牌的价值w之和的最大值.

很明显是一道类似于背包的dp题,只不过这道题要开三维

和背包一样,我们设 f[i][j][k] 是从前i个物品中选的最大价值,其中j代表有我们把其中的j个点数翻倍了,很明显j大于等于0,小于等于m

第三维是两堆价值的插值,由题意得在-2600和2600之间,我们直接把他加2600,变成0-5200

然后是转移方程

不选第i个时: f[i] [j] [k] = f[i-1] [j] [k]

选第i个放入一背包 f[i] [j] [k] = max(f[i] [j] [k],f[i-1] [j] [k-t[i]]+v[i])

选第i个放入第二个背包 f[i] [j] [k] = max(f[i][j][k],f[i-1][j][k+t[i]]+v[i])

选第i个放入第一个背包并翻倍 f[i] [j] [k] = max(f[i] [j] [k],f[i-1] [j-1] [k-2*t[i]]+v[i])

选第i个放入第二个背包并翻倍 f[i] [j] [k] = max(f[i] [j] [k],f[i-1] [j-1] [k+2*t[i]]+v[i])

直接开三维空间会炸,由于我们求i是只用到i-1的状态,所以直接开2,上下循环即可

关键代码如下

const int N=105,M=6005;
const int inf=-1e18
int t[N],v[N];
int f[2][N][M];
signed main()
{
	int n,m;
	cin>>n>>m;
	fer(i,1,n) {
        cin>>v[i]>>t[i];
    }
	fer(i,0,m){			
		fer(j,0,5200){
            if(j==2600) continue;
			f[0][i][j]=inf;
        }
    }
	fer(i,1,n)
		fer(j,0,m)
			fer(k,0,5200)
			{
                int x;
                if(i&1) x=1;
                else x=0;
				f[x][j][k]=f[x^1][j][k];
				if(k-t[i]>=0){
                    f[x][j][k]=max(f[x][j][k],f[x^1][j][k-t[i]]+v[i]);
                }
				if(k+t[i]<=5200){
                    f[x][j][k]=max(f[x][j][k],f[x^1][j][k+t[i]]+v[i]);
                }
				if(j)
				{
					if(k-2*t[i]>=0)
						f[x][j][k]=max(f[x][j][k],f[x^1][j-1][k-2*t[i]]+v[i]);
					if(k+2*t[i]<=5200)
						f[x][j][k]=max(f[x][j][k],f[x^1][j-1][k+2*t[i]]+v[i]);
				}
			}
	cout< 

H Life is a Game:

 题意为给定一张无向图,每个点有点权每条边有边权

规则是每次给我们一个初始能量值,每到一个新的点,我们的能量就可以加上这个点的点权,但我们要想走过一条边,需要能量大于该边的权值,问我们每次最大能获得多少能量

首先暴力的想,由于获取能量的路径只有到达点,那我们肯定要尽可能多的到达点,但边会限制我们到达新的点

第一步我们肯定要把整张图变成一颗最小生成树,因为经过边权大的边对我们毫无意义,我们只需要花最小代价去最多的点即可

其次我们肯定会想到写bfs来判断某个点能不能达到,但1e5个询问,每次最坏为n,总复杂度为n*n肯定过不了

这时候我们可以引入一个数据结构————kruskal重构树

详解可以百度一下或者去看->

(31条消息) Kruskal重构树详解_lxrrrrrrrr的博客-CSDN博客

在这棵树上除了代表原图点的叶子节点,上面点的点权必定大于他子树所有点的点权

也就是说,只要能量大于虚点A的点权,那么A的子树的能量我们都可以获取到,这个可以倍增时预处理一下

由于其独特的性质,在处理这道题中,我们可以每次通过起始点尽可能往上跳,每次跳完我们的能量就可以变成跳之后点的子树权值,直至跳不上去或跳到根节点,

这样我们可以把复杂度压到nlogn

代码如下:

#include 
#include 
#define fer(i,a,b) for(int i=a;i<=b;i++)
#define der(i,a,b) for(int i=a;i>=b;i--)
#define int long long
#define pb push_back
#define pll pair
#define ld long double
using namespace std;
const int N =200010,M=100010;
int n,m,k;
struct edge
{
    int a,b,w;
    bool operator<(const edge& ee)const { return w < ee.w; }
}ed[M];
vector e[N];
int a[N], p[N], stw[N];
int d[N];
int fa[N][18];
int find(int x) {
    return p[x]==x?p[x]:p[x]=find(p[x]);
}
int exkruskal() {
    sort(ed+1,ed+1+m);
    fer(i,1,2*n) p[i]=i;
    int cnt=n;
    fer(i,1,m) {
        int a=find(ed[i].a),b=find(ed[i].b);
        if (a^b){
            d[++cnt]=ed[i].w;
            p[a]=p[b]=cnt;
            e[cnt].push_back(a);
            e[cnt].push_back(b);
        }
    }
    return cnt;
}
int exlca(int x) { 
    int sum=a[x];
    for (auto j :e[x]){
        fa[j][0]=x;
        for (int i=1;i<=17;i++)
            fa[j][i]=fa[fa[j][i-1]][i-1];
        sum+=exlca(j);
    }
    stw[x]=sum;
    return sum;
}
signed main() {
    ios::sync_with_stdio(false);
    cin>>n>>m>>k;
    fer(i,1,n) cin>>a[i];
    fer(i,1,m) {
        int a,b,x;
        cin>>a>>b>>x;
        ed[i] = {a,b,x};
    }
    n=exkruskal();
    exlca(n);
    d[0] = 1e18;
    fer(i,1,k) {
        int x,q;
        cin>>x>>q;
        int ans=a[x]+q;
        while (x!= n)
        {
            int x1=x;
            for (int i=17;i>=0;i--)
                if (d[fa[x][i]]<=ans) x=fa[x][i];
            if (x==x1) break;
            ans=stw[x]+q;
        }
        cout<

K Circle of Life:

构造题,大致意思是给一个01串,每一次变换之后第i个1会分裂成两个1到i-1和i+1,如果两个分裂的撞在一起了则会消失,要求我们输出一个n位的01串,使其在2*n次变换之内可以变回原样,即出现循环

首先这种构造题一般都是有结论性的特殊序列

比如11,00,101,010这种,根据题意首先不可能全是1或0

010一次会变成101,第二次会直接000,也肯定不行

而我们发现,像1001,0110这两个串,他们是在此变换中互逆的,可以考虑用他们构造循环

1001-> 0110 -> 1001

100110-> 011001 -> 100110

我们发现对于n是偶数,我们只需要不断交错添加01和10,即可构造成功

而对于奇数 0101001 -> 1000110 -> 0101001 也能循环

我们发现前面加个010,再结合后面偶数的答案拼接在一起,即可构造

所以我们对于n是奇数,先输出010,然后n-=3,在和偶数一起算即可

偶数直接无脑10加01加10

代码如下:

signed main(){
    int n;
	cin>>n;
	if(n==1||n==3){
        cout<<"Unlucky";
    }
	else{
		if(n%2)	
        {
            cout<<"010";
            n-=3;
        }
		n/=2;
		fer(i,0,n-1)
			if(i&1){
                cout<<"01";
            }
			else cout<<"10";
	}

}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/893286.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号