【习题总结】推公式问题

  在算法竞赛问题中,手推公式是一项及其重要的技能,下至签到题,上至高级数学、数据结构、动态规划,都需要扎实的推公式能力。
  本文章先从简单题入手,总结常见的推公式思路。
例题1:乘法的妙用
题目来源:
https://codeforces.com/contest/1715/problem/C
题意:
  记 g ( l , r ) g(l,r) g(l,r)为区间中不同连续相等子区间的个数,求 ∑ l = 1 n ∑ r = l n g ( l , r ) \sum_{l=1}^{n} \sum_{r=l}^{n} g(l,r) l=1nr=lng(l,r)
思路:
  朴素的做法是枚举区间右端点,算出 ∑ g ( l , r i ) \sum g(l,r_i) g(l,ri)值,依次相加。这样做很容易发现 r i r_i ri r i − 1 r_{i-1} ri1之间有重复计算的部分。找找规律,分类讨论 a i a_i ai a i − 1 a_{i-1} ai1相等和不相等的情况,即可 O ( n ) O(n) O(n)求出 ∑ g ( l , r ) \sum g(l,r) g(l,r)
  考虑 O ( 1 ) O(1) O(1)查询,其实修改后只会影响重复运算的部分,故用乘法,加上/减去 i × ( n − i ) i \times (n-i) i×(ni)即可。

  code:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int maxn=1e5+10;
    LL n,q;
    LL a[maxn];
    int main()
    {
    	ios::sync_with_stdio(false);
    	cin>>n>>q;
    	for(LL i=1;i<=n;i++)
    		cin>>a[i];
    	LL add=1,ans=add;
    	for(LL i=2;i<=n;i++)
    	{
    		if(a[i]!=a[i-1])
    			add+=i;
    		else
    			add+=1;
    		ans+=add;
    	}
    	while(q--)
    	{
    		LL id,x;
    		cin>>id>>x;
    		if(a[id]==a[id-1] &&x!=a[id-1])
    			ans+=(id-1)*(n-id+1);
    		else if(a[id]!=a[id-1] && x==a[id-1])
    			ans-=(id-1)*(n-id+1);
    		if(a[id]==a[id+1] &&x!=a[id+1])
    			ans+=id*(n-id);
    		else if(a[id]!=a[id+1] && x==a[id+1])
    			ans-=id*(n-id);	
    		cout<<ans<<'\n';	
    		a[id]=x;
    	}
        return 0;
    }

例题2:结合差分,前缀和
若推出的公式会TLE,要么用其他技巧变换公式,要么用数据结构优化(后面会提及),要么差分/前缀和。
题目来源:
https://codeforces.com/contest/1715/problem/C
题意:
  求矩阵中相同元素的曼哈顿距离之和。
思路:
  朴素的做法是对于每一种元素,枚举每一列/行,个数 × \times ×距离作累加,显然会TLE。
  考虑注意到各个元素的列数可以预处理出来,因此求距离可以采用距离总和—距离前缀和的方式。
  编程时,可以将相同元素的列数存放到一起,排序,作为前缀和数组。
  code:

    #include <bits/stdc++.h>
    using namespace std;
    const int N=500;
    const int M=1e5+10;
    typedef pair<int,int> PII;
    typedef long long ll;
    int n,m;
    int a[N][N];
    vector <int> row[M];
    vector <int> col[M];
    ll res;
    ll cal(vector<int> a)
    {
        ll sum=0,k=a.size();
        sort(a.begin(),a.end());
        vector<ll> S(k+1);
        for(int i=1;i<=k;i++)
            S[i]=S[i-1]+a[i-1];
        for(int i=1;i<=k;i++)
            sum+=(ll)(i-1)*a[i-1]-S[i-1];
        return sum;
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                cin>>a[i][j];
                row[a[i][j]].push_back(i);
                col[a[i][j]].push_back(j);
            }
        }
        for(int color=1;color<=1e5;color++)
        {
            res+=cal(row[color])+cal(col[color]);
        }
        cout<<res<<endl;
        return 0;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值