解法——逆向思维

很多时候,解题需要逆向思维。

最为普遍的就是出现在容斥的题目里面,其他题型的题目逆向思维也会有可能用到,当我们遇到这种题目的时候往往正向解题时无思路或者十分复杂,但是运用逆向思维会变得轻松很多。

1.容斥

同色三角形问题 https://ac.nowcoder.com/acm/contest/4137/F

经典题目,同色三角形个数=所有三角形个数-不同色三角形个数

我们可以看到这题不同色的情况只会有两种:一种是两黑一白,另一种是两白一黑

对于其中一种情况,我们这么处理:

对于三角形的三个点,每个点与之连接两条边,如果两条边颜色不同则sum+1,那么一个不同色的三角形,统计出来sum=2。

那么只需最后sum/2,即得出一个不同色三角形个数。

#include <bits/stdc++.h>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 5010;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
int du[maxn][2],i,j;
ll n,A,B,C,P,D,ans,now;
int main()
{
    n=read();
    ans=0;
    A=read(),B=read(),C=read(),P=read(),D=read();
    for(i=1;i<=n;i++)
    {
        for(j=i+1;j<=n;j++)
        {
            now=(A*(i+j)%P*(i+j)%P+B*(i-j)%P*(i-j)%P+C)%P;
            if(now<=D)
            {
                du[i][0]++;
                du[j][0]++;
            }
            else 
            {
                du[i][1]++;
                du[j][1]++;
            }
        }
    }
    for(i=1;i<=n;i++)
        ans+=du[i][0]*du[i][1];
    cout<<n*(n-1)*(n-2)/6-ans/2<<endl;
    return 0;
}
View Code

2.搜索

2015-2016 ACM-ICPC, NEERC, Moscow Subregional Contest K. King’s Rout

题意:希望你构造出一种符合以下条件的拓扑序。

1、拓扑序

2、在满足上述条件的情况下,让1尽可能地靠前。

3、在满足上述条件的情况下,让2尽可能地靠前。

n、在满足上述条件的情况下,让n尽可能地靠前。

如果正着拓扑,那么我们是很难得出答案的,我们来看一个例子

 如果只是贪心的取当前最小值来拓扑,则得出序列是【5,2,3,1,4,6,7】,而答案得出的序列是【5,3,1,2,4,6,7】,所以正着拓扑的话,很难处理

那逆着建图,逆着拓扑呢?我们逆着拓扑取当前最大值来拓扑会不会得出答案序列?答案是会的。

因为我早点找出当前最大值,就等价于把小的值留在最后面,把这种方法得出来的序列倒过来看,就是答案要求的序列。

#include <bits/stdc++.h>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
priority_queue<int> q;
map<pii,int> mapp;
vector <int> G[maxn];
int n,du[maxn],m,a,b,ans[maxn],cnt,i;
void toposort()
{
    for (ri i=1;i<=n;i++)
    {
        if (du[i]==0) q.push(i);
    }
    while (!q.empty())
    {
        int u=q.top();
        q.pop();
        ans[cnt++]=u;
        for (auto v:G[u])
            if (--du[v]==0) q.push(v);
    }
}
int main()
{
    n=read(),m=read();
    for (i=1;i<=m;i++)
    {
        a=read(),b=read();
        G[b].push_back(a);
        du[a]++;
    }
    
    toposort();
    for (i=cnt-1;i>=0;i--) cout<<ans[i]<<" ";
    return 0;
}
View Code

2014-2015 ACM-ICPC, Asia Xian Regional Contest H. The Problem to Make You Happy

题意:在一张有向图,在图中的两个节点上面有两个棋。Alice和Bob在上面移动棋子,如果Bob不能移动或是两个棋子在同一个节点上,那就算Bob输。

用原来计算SG函数的方法进行记忆化搜索?这样做很烦很乱。

那正着来想一筹莫展,逆着来呢?

Bob输有两种方式,一种不能移动另一种两个棋子在同一个节点上,我们把这两种方式做为必败态的起点,进而一个个推导出能到达必败态的每一个状态。

设状态f[bob‘s当前所在点][alice‘s当前所在点][谁正在下棋]

那么 f[bob‘s当前所在点][alice‘s当前所在点][bob 正在下] 能推导出 f[bob‘s当前所在点][alice‘s上一个所在点][alice 正在下],同理另一种状态

#include <bits/stdc++.h>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 105;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
struct node
{
    int x,y,step;
};
int t,n,m,u,v,du[maxn],i,j,a,b,cnt[maxn][maxn],x,cas,y,f[maxn][maxn][2];
bool G[maxn][maxn];
int main()
{
    t=read();
    while (t--)
    {
        n=read(),m=read();
        memset(G,false,sizeof(G));
        memset(du,0,sizeof(du));
        memset(f,0,sizeof(f));
        memset(cnt,0,sizeof(cnt));
        while (m--)
        {
            x=read(),y=read();
            G[y][x]=true;
            du[x]++;
        }
        a=read(),b=read();
        queue<node> q;
        for (i=1;i<=n;i++)
        {
            f[i][i][0]=1;
            q.push({i,i,0});
            f[i][i][1]=1;
            q.push({i,i,1});
        }
        for (i=1;i<=n;i++)
            for (j=1;j<=n;j++)
                if (i-j && du[i]==0)
                {
                    f[i][j][0]=1;
                    q.push({i,j,0});
                }
        while (!q.empty())
        {
            node u=q.front();
            q.pop();
            x=u.x,y=u.y;
            if (u.step==1)
            {
                for (j=1;j<=n;j++)
                    if (G[x][j])
                    {
                        if (++cnt[j][y]==du[j])//如果从j出发的所有的状态都是必败态,那么f[j][y][0]本身也是必败态
                        {
                            if (f[j][y][0]) continue;
                            f[j][y][0]=1;
                            q.push({j,y,0});
                        }
                    }
            }
            else
            {
                for (j=1;j<=n;j++)//Alice可以选择一条路使得Bob必败
                    if (G[y][j])
                    {
                        if (f[x][j][1]) continue;
                        f[x][j][1]=1;
                        q.push({x,j,1});
                    }
            }
        }
        printf("Case #%d: ",++cas);
        if (f[a][b][0]) cout<<"No"<<endl;
            else cout<<"Yes"<<endl;
    }
    return 0;
}
View Code

3.记录变量方面

HDU - 5245

题意:给你一个M×N的矩阵,你可以选K次,每次选择两个点(x1,y1)和(x2,y2),将这两个点围成的子矩阵涂上颜色,求预计涂色的格子的个数。

因为每个格子若能被涂色,都会结果造成贡献,所以一个一个格子来考虑。

我们可以考虑先计算一个格子不被涂色的概率,然后再1-不涂色概率就得出此格子被涂色的概率

如图,我们计算红色格子不被涂色的概率,那么就要算出选择的两个点(x1,y1)和(x2,y2)都是黄色格子的概率,把其切成几个矩形来计算即可。

#include <bits/stdc++.h>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
double qpow(double p,ll q){return (q&1?p:1)*(q?qpow(p*p,q/2):1);}
ll m,n,k,i,j,q,t;
double sum,num;
int main()
{
    t=read();
    for (q=1;q<=t;q++)
    {
        m=read(),n=read(),k=read();
        sum=num=0;
        for (i=1;i<=m;i++)
            for (j=1;j<=n;j++)
            {
                num+=(i-1)*n*(i-1)*n;
                num+=(j-1)*m*(j-1)*m;
                num+=(m-i)*n*(m-i)*n;
                num+=(n-j)*m*(n-j)*m;
                num-=(i-1)*(j-1)*(i-1)*(j-1);
                num-=(i-1)*(n-j)*(i-1)*(n-j);
                num-=(m-i)*(j-1)*(m-i)*(j-1);
                num-=(m-i)*(n-j)*(m-i)*(n-j);
                num/=n*m*n*m;
                sum+=1.0-qpow(num,k);
            }
        printf("Case #%d: %.0lf
",q,sum);
    }
    return 0;
}
View Code

2015-2016 ACM-ICPC, NEERC, Moscow Subregional Contest H. Hashing

题意:给出N个16进制的数,从中选出一个序列,问Σ j^(a[i]) 最大是多少,a[I]是选出来的数,j是这个数在序列中是第J个(从0开始计数)

我们发现,如果我们记录选中了多少个是O(n^2)的空间复杂度,是肯定不可行的,时间复杂度也不够。 我们可以猜想:不被选中的数的个数特别少。少到多少呢?这个不太好猜,但是肯定不会大于256。 所以我们就可以记录不被选中的数。 因此就可以用dp直接做了。

#include <bits/stdc++.h>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5+5;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
int n,a[maxn],i,j;
ll ans,f[maxn][260];
int main()
{
    n=read();
    for (i=1;i<=n;i++) scanf("%x",&a[i]);
    for (i=1;i<=n;i++)
        for (j=0;j<=min(i,256);j++)
        {
            if (j>0) f[i][j]=f[i-1][j-1];
            f[i][j]=max(f[i][j],f[i-1][j]+(a[i]^(i-j-1)));
            ans=max(ans,f[i][j]);
        }
    cout<<ans<<endl;
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/Y-Knightqin/p/12717067.html