首页 > 其他分享 >POI2019

POI2019

时间:2023-09-25 10:16:03浏览次数:32  
标签:POI2019 个点 int ll mid times lld

P6661 Pomniejszenie

还算正常的贪心
P6661
修改\(A\)与\(B\)高位尽可能多的数字相同,从第\(p\)位开始不同,第\(p\)位满足\(a[i]<b[i]\),把从\(p+1\)位到最后一位的数尽可能多的改成\(9\)。
现在考虑位置\(p\)的选择:

  • \(p\)满足的条件:1. 从\(1\)到\(p\)中\(a[i]\)与\(b[i]\)不同(需要修改)的数量\((k_1)\)不超过\(k\)。2. 从\(p+1\)到\(n\)(可能被修改)的数量不小于还需要修改的次数。
  1. 在\(b[i]=0\)时\(a[i]\)做不到小于\(b[i]\),所以\(i\)不可以作为\(p\)。
  2. \(b[i]>a[i]\)时,\(a[i]\)可以不修改,所以只需要满足\(k_1<=k\)并且\(n-i>=k-k_1\)。
  3. 在\(a[i]=0\)并且\(b[i]=1\)时不可修改,剩下的情况可以做修改,所以要满足\(k_1+1<=k\)并且\(n-i>=k-k_1-1\)。

在\(i<p\)时,答案为\(b[i]\)。
在\(i=p\)时,如果这一位为了确保达到修改次数必须修改并且\(b[p]-1=a[p]\),答案为\(b[p]-2\),其余情况答案为\(b[p]-1\)。
在\(i>p\)时,如果\(a[i]=9\)并且\(a[i]\)必须修改时答案为\(8\),其余情况在还有修改机会剩余时答案为\(9\)。
在无修改机会剩余时答案为\(a[i]\)。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int t,k;
char a[100010],b[100010];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s%s%d",a+1,b+1,&k);
        int n=strlen(a+1);
        int p=0,k1=0;
        for(int i=1;i<=n;i++)
        {
            if(b[i]!='0')
            {
                if(a[i]<b[i]&&k1<=k&&n-i>=k-k1)p=i;

                if((a[i]>'0'||b[i]>'1')&&k1+1<=k&&n-i>=k-k1-1) p=i;
            }
            if(a[i]!=b[i])k1++;
        }
        if(!p) {printf("-1\n");continue;}
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            if(i<p) ans=b[i]-'0';
            else if(!k) ans=a[i]-'0';
            else if(i==p) ans=b[i]-'0'-1-(a[i]+1==b[i]&&n-i+1==k);
            else ans=9-(a[i]=='9'&&n-i+1==k);
            k-=ans!=a[i]-'0';
            cout<<ans;
        }
        printf("\n");
    }
}

P6659 Najmniejsza wspólna wielokrotność

不太正常的二分
P6659
在区间长度大于\(3\)时对于极限的\(n\)为\(10^{18}\)答案内的数只会在\(10^{6}\)以内,可以暴力枚举左右端点存在\(map\)里。
区间长度为\(1\)的区间中只有\(x\)和\(x+1\)而且\(lcm(x,x+1)=x\times (x+1)\),可以二分找到答案。
区间长度为\(2\)的区间中\(x,x+1,x+2\)的最小公倍数在\(x\)为奇数时是三个数的乘积,在\(x\)为偶数时是三个数乘积的一半,同样可以二分找到区间端点。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
const ll maxm=1e18+5;
ll z,n;
struct node
{
    ll l,r;
};
map<ll,node> m;
ll solve1()
{
    ll l=1,r=1e9;
    while(l<r)
    {
        ll mid=(l+r+1)>>1;
        if(mid*(mid+1)>n) r=mid-1;
        else l=mid; 
    }
    if(l*(l+1)==n) 
    {
        printf("%lld %lld\n",l,l+1);
        return 1;
    }
    return 0;
}
ll solve2(int o)
{
    ll l=1,r=2e6;
    while(l<r)
    {
        ll mid=(l+r+1)>>1;
        if(mid*(mid+1)*(mid+2)>n) r=mid-1;
        else l=mid;
    }
    if (((int)l&1)!=o) return 0;
    if(l*(l+1)*(l+2)==n) 
    {
        printf("%lld %lld\n",l,l+2);
        return 1;
    }
    return 0;
}
signed main()
{
    for(ll i=1;i<=1000010;i++)
    {
        ll w=i*(i+1);
        for(ll j=i+2;j<=100010;j++)
        {
            ll g=__gcd(j,w);
            w=w/g;
            if(w>maxm/j)break;
            w=w*j;
            if(m.find(w)==m.end())
            {
                m[w].l=i; m[w].r=j;
            }
        }
    }
    scanf("%lld",&z);
    while(z--)
    {
        scanf("%lld",&n);
        if(m.find(n)!=m.end())
        {
            printf("%lld %lld\n",m[n].l,m[n].r);
            continue;
        }
        if (solve1()) continue;
        if (solve2(1)) continue;n*=2;
        if (solve2(0)) continue;
        cout<<"NIE"<<endl;
    }
}

P6663 Układ scalony

大诡异的构造
P6663

题意概括

在\(n\times m\)的网格图中每个格子为一个点,只向相邻的格子连边权为\(1\)的边,使其直径恰好为\(k\)。

题解

把\(n\times m\)个点连接成树,直径最长的情况是把所有点串成一条链,此时直径为\(n\times m-1\)。直径最短的情况是把距离最远的点(如左下角和右上角)连起来,此时直径为\((n-1)+(m-1)\),对于\(n\)和\(m\)都是偶数的情况,其他点与找到的直径相连时会使直径增长\(1\)。
有图有真相:
image
最短直径的寻找方式:尽可能串连一排把图平均分成两个部分,再在两端分别向左右延申到网格边缘。
右图\(m\)为奇数,最短直径(可以)是蓝色线条,长度为\(n+m-2\)。
左图\(m\)和\(n\)都是偶数,浅蓝色线条和部分深蓝色线条直接连接左下角和右上角的点,长度没有连接左上角和左下角的深蓝色线条长,由于深蓝色竖线尽可能居中安置,真实直径(深蓝色)会比\(n+m-2\)(浅蓝色加部分深蓝色)多一。
构造方式:
先把左上角和右下角的点按照(形似)上述构造最短直径(对于\(n\)和\(m\)都是偶数的情况是长度少一的部分直径)的方式连接,剩下的直接(暂时)像鱼刺一样连接在所画的链上(保证尽量不影响直径长度),如果现在连接出来的链长度小于\(k\),使一头从外向内卷曲(从外围缩减鱼刺的长度,把断掉的部分接到链上),链的长度到达\(k\)后,剩余的鱼刺按照鱼刺样式相连。
诡异的图解:
image

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int maxn=1e3+10;
int vis[maxn][maxn];
int f;
void add(int a,int b,int a1,int b1)
{
	if(f) swap(a,b),swap(a1,b1);
	printf("%d %d %d %d\n",a,b,a1,b1);
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	if(k>=n*m||k<n+m-1-((n&1)||(m&1))) {printf("NIE"); return 0;}
	printf("TAK\n");
	if(m&1) swap(n,m),f=1;
	int mid=n/2+1;
	k-=n+m-2;
	int a=min(k,(mid-1)*(m-1)),b=k-a;
	if(n%2==0&&k<m-1) a--;
	int x=1,y=1;
	while(a--)
	{
		int x1=x,y11=(x&1)?y+1:y-1;
		if(y11==m+1) y11=m,x1++;
		if(y11==1) y11=2,x1++;
		add(x,y,x1,y11);
		x=x1; y=y11; 
		vis[x][y]=1;
	}
	x=n; y=m;
	while(b--)
	{
		int x1=x,y11=((n-x)&1)?y+1:y-1;
		if(y11==m) y11=m-1,x1--;
		if(y11==0) y11=1,x1--;
		add(x,y,x1,y11);
		x=x1; y=y11;
		vis[x][y]=1;
	}
	for(int i=1;i<m;i++)
	{
		add(mid,i,mid,i+1);
	}
	for(int i=1;i<n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(!vis[i][j]&&!vis[i+1][j]) add(i,j,i+1,j);
		}
	}
}

P6662 Przedszkole

缝合怪警告
P6662

题意概括:

在有\(n\)个点\(m\)条无向边的途中,给每个节点染色,至多使用\(k\)种颜色,要求一条边所连接的两个节点颜色不能相同。(多组询问\(n,m\)和连边方式不变,\(k\)不同)
缝合部分(残肢):

  1. \(n\leq 15\)
  2. \(m\leq 24\)
  3. 图只由环组成。

题解

1.

对于第一部分,由于\(n\)比较小,可以用状态压缩枚举每一个点的状态。设\(f_{i,s}\)为已经使用了\(i\)种颜色,节点状态为\(s\)(\(0\)表示未涂色,\(1\)表示已涂色)的方案数。转移时枚举\(s\)的子集\(t\),把\(t\)中为\(1\)的点涂成相同的颜色,预处理每个点不能和哪些点连边,先枚举每一种状态并排除不合法的状态,再用合法(\(k\)内部的点不互相连边,\(t\)不为空集)的\(t\)转移,转移方程为:\(f_{(i,s)}=f_{(i,s)}+f_{(i-1,s-t)}\)。最后求出答案(用从\(1\)到\(k\)种颜色,给每一个点都染色的方案数累加):\(ans=\sum\limits_{i=1}^{n}\times C_{k}^{i}\times f_{(i,2^n-1)}\)。时间复杂度为:\(O(n3^m+Tn^2)\)

2.

对于第二部分,由于\(m\)比较小考虑\(O(n^2)\)枚举,可以枚举每一条边所连的点的状态,在用总数减去不合法的方案数。
现在考虑不合法的方案数的求解。设\(A_i\)表示至少有\(i\)个点不合法的方案集合,\(c_i\)为正好有\(i\)个点不合法的方案数,\(A_i\)集合的大小\(|A_i|=c_{n-i}\times k^{n-i}\)(正好有\(i\)个点不合法,其余的点任意涂色),根据容斥原理\(|\bigcup\limits_{i=1}^{n}A_i|=\sum\limits_{m=1}^{n}(-1)^{m-1}\sum\limits_{a_i<a_i+1}|\bigcap\limits_{i=1}^{m}A_{a_i}|\),得到\(|\bigcup\limits_{i=1}^{n}A_i|=\sum\limits_{j=1}^{n}(-1)^{j-1}|A_j|\),最终答案为\(k^n-|\bigcup\limits_{i=1}^{min(n,m)}A_i|\)
现在稍微化简一下:

\[\begin{aligned} \text{原式} &=|\bigcup\limits_{i=0}^{min(n,m)}A_i|\\ &=\sum\limits_{i=0}^{min(n,m)}(-1)^i\times c_{n-i}\times k^{n-i} \end{aligned}\]

预处理出\(c_i\),遍历每一条边,分为端点颜色相同和颜色不同两种情况向下递归,遍历了偶数条边时加上方案数,遍历了奇数条边时减去方案数,用可撤销并查集维护每个点的颜色,递归时如果一条边的两个点颜色已经相同,直接返回。时间复杂度为$O(n3n+Tn2)。

3.

对于第三部分,图由很多环组成。先单独考虑一个环的方案数,设\(f_i\)为一个长度为\(i\)的环的方案数,转移时如果第\(1\)个点和第\(i-1\)个点颜色相同,第\(i\)个点就有\(k-1\)种方案,薅掉第一个点,剩下的就是一个由\(i-2\)个点组成的环,如果第\(1\)个点和第\(i-1\)个点颜色不同,第\(i\)个点有\(k-2\)种方案,剩下\(i-1\)个点可以直接点扣成环,所以\(f_i=(k-1)\times f_{i-2}+(k-2)\times f_{i-1}\)。
现在为了保证时间复杂度不炸裂,让我们发挥高贵的高中牲必备的数学水平,从二阶递推公式求解通项公式,首先解特征方程\(x^2=(k-2)x+k-1\)解得\(x_1=-1,x_2=k-1\),把\(f_2=k(k-1)=k^2-k\),\(f3=k(k-1)(k-2)=k^3-3k^2+2k\)代人\(f_n=c_1\times (k-1)^n+c_2\times (-1)^n\),解出\(c_1=1\),\(c_2=k-1\),所以\(f_n=(k-1)^n+(-1)^n(k-1)\)。
最终答案为所有环的方案数乘积。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
const int maxn=1e5+10;
int n,m,z;
const ll mod=1e9+7;    
ll qpow(ll a,int b)
{
    ll tmp=1;while(b){if(b&1) tmp=(tmp*a)%mod; a=a*a%mod; b=b>>1; } return tmp%mod;
}
ll C(int x,int y)
{
    if(x<y)return 0;
    ll tmp=1;
    for(int i=1;i<=y;i++)
    {
        tmp=tmp*(x-i+1)%mod*qpow(i,mod-2)%mod;
    }
    return tmp;
}
namespace task1
{
    int p[maxn],vis[1<<17],f[17][1<<17];
    void main()
    {
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%lld%lld",&u,&v); u--; v--;
            p[u]|=(1<<v); p[v]|=(1<<u);
        }
        for(int s=0;s<(1<<n);s++)
        {
            for(int i=0;i<n;i++)
            {
                if(((1<<i)&s)&&(p[i]&s)) vis[s]=1;
            }
        }
        f[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int s=0;s<(1<<n);s++)
            {
                for(int j=s;j!=0;j=(j-1)&s)
                {
                    if(!vis[j]) f[i][s]=(f[i][s]+f[i-1][s-j])%mod;
                }
            }
        }
        for(int i=1;i<=z;i++)
        {
            ll ans=0;
            int k;scanf("%lld",&k);
            for(int j=1;j<=n;j++)
            {
                ans=(ans+C(k,j)*f[j][(1<<n)-1]%mod)%mod;
            }
            printf("%lld\n",ans);
        }
    }
}
namespace task2
{
    struct node
    {
        int u,v;
    };
    node e[maxn];
    int fa[maxn],c[maxn];
    int getfa(int x)
    {
        return fa[x]==x?x:getfa(fa[x]);
    }
    void dfs(int x,int cnt,int op)
    {
        if(x==m+1) {c[cnt]+=op;return;}
        int uu=getfa(e[x].u),vv=getfa(e[x].v);
        if(uu==vv) return;
        else
        {
            dfs(x+1,cnt,op);
            fa[vv]=uu;
            dfs(x+1,cnt-1,-op);
            fa[vv]=vv;
        }
    }
    void main()
    {
        for(int i=1;i<=m;i++)
        {
            scanf("%lld%lld",&e[i].u,&e[i].v);
        }
        for(int i=1;i<=n;i++) fa[i]=i;
        dfs(1,n,1);
        for(int z1=1;z1<=z;z1++)
        {
            int k; scanf("%lld",&k);
            ll ans=0;
            for(int i=0;i<=min(n,m);i++)
            {
                ans=(ans+c[n-i]*qpow(k,n-i)%mod+mod)%mod;
            }
            ans=(ans%mod+mod)%mod;
            printf("%lld\n",ans);
        }
    }
}
namespace task3
{
    struct node
    {
        int to,nxt;
    };
    node t[2*maxn];
    int head[maxn],tot=1;
    void add(int u,int v)
    {
        tot++; t[tot].to=v; t[tot].nxt=head[u]; head[u]=tot;
    }
    int vist[2*maxn],dfn[maxn],dfn1,low[maxn],sz[maxn],scc[maxn],vis[maxn],s[maxn],tp,scc1;
    void tarjan(int u)
    {
        dfn[u]=low[u]=++dfn1; s[++tp]=u; vis[u]=1;
        for(int i=head[u];i!=0;i=t[i].nxt)
        {
            int v=t[i].to;
            if(!vist[i])
            {
                vist[i]=vist[i^1]=1;
                if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
                else if(vis[u]) low[u]=min(low[u],dfn[v]);
            }
        }
        if(dfn[u]==low[u])
        {
            scc1++;
            while(s[tp]!=u)
            {
                scc[s[tp]]=scc1; sz[scc1]++; vis[s[tp]]=0; tp--;
            }
            scc[s[tp]]=scc1; sz[scc1]++; vis[s[tp]]=0; tp--;
        }
    }
    ll f(int x1,int k1)
    {
        return (qpow(k1-1,x1)+((x1&1)?-1:1)*(k1-1))%mod;
    }
    void main()
    {
        for(int i=1;i<=m;i++)
        {
            int u,v; scanf("%lld%lld",&u,&v);
            add(u,v); add(v,u);
        }
        for(int i=1;i<=n;i++)
        {
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=z;i++)
        {
            int k; scanf("%lld",&k);
            ll ans=1;
            for(int j=1;j<=scc1;j++)
            {
                ans=ans*f(sz[j],k)%mod;
            }
			ans=(ans%mod+mod)%mod;
            printf("%lld\n",ans);
        }
    }
}
signed main()
{
    scanf("%lld%lld%lld",&n,&m,&z);
    if(n<=15)
    {
        task1::main();
        return 0;
    }
    if(m<=24)
    {
        task2::main();
        return 0;
    }
    task3::main();
    return 0;
}

标签:POI2019,个点,int,ll,mid,times,lld
From: https://www.cnblogs.com/PHOTONwa/p/17716848.html

相关文章