【codechef】Feb.Challenge【逆元,超时题等】

B.https://www.codechef.com/problems/STROPR

题意:将数列{x1,x2,..,xn}进行前缀累加操作,得到{s1,s2,..,sn},求这样操作k次后数列中第m个值。

画出每次操作后第m个数所包含初始数列{x1,x2,..,xn}里每个数的个数:

       ....    m-2 m-1 m     ← m     

1    1    1    1    1    1           1

6    5    4    3    2    1    k↓   2

21  15  10   6    3    1  3

56  35  20  10   4    1  4

观察每行从右往左[1,2,3,4],[1,3,6,10],[1,4,10,20],[1,5,10,15],[1,6,21,66]的规律,发现是n*(n+1)*(n+2)*(n+k-1)/k!,k是从上往下第几层。

于是求【第k行第m个】的问题转化成了求【前m个数分别乘上对应上表k行前m个数】

我晕。。明明在zju那时可以迅速想到的,这次用到这个公式的时候反应竟然奇慢无比,一直在想n*a1+n*(n+1)/2!*a2+n*(n+1)*(n+2)/3!*a3+...以累乘的方式求解又会导致除法取模错误睡觉

以后记住:碰到类似这种n*(n+1)*(n+2)*...*(n+m-1)/m!务必第一时间想到组合公式C(n+m-1,m)

————————————————————————————————————————————

但这题的mod是1000000007(太大了),用lucas不幸死在超时。所以上述方法本题不适用!正解是老老实实手动逆元打表(这居然是第二题该有的做法?!)所以,,非常有用的逆元模板。

#include<bits/stdc++.h> 
#define ll long long
#define maxn 10000005
#define mod 1000000007
#define eps 1e-12
using namespace std; 
ll x[100005];
//【求逆元】模板 
ll inverseFactorial[maxn+1];
ll inverseModulo(int b)  //b的逆元 
{
	ll x = 0, y = 1, r, q;
	int a = mod;
	while(b){
		q = a/b;
		r = a%b;
		a = b;
		b = r;
		r = x;
		x = y;
		y = r - q * y;
	}
	if(x < 0) x += mod;
	return x;
}
 
void factorialInv(void)  //求阶乘n!的逆元 
{
	ll ret = 1;
	inverseFactorial[0]=ret;
	for(int i = 1; i < maxn; i++)    
	{
		ret = ret  * inverseModulo(i) % mod; 
		inverseFactorial[i]=ret;
	}
}
 
int main(){
	factorialInv();
	int t;
	scanf("%d",&t); 
	while(t--){	
		int n,m;
		ll k;
		scanf("%d%d%lld",&n,&m,&k);
		k%=mod;
//		cin>>n>>m>>k;
		for(int i=1;i<=n;++i){
			scanf("%lld",&x[n-i+1]); 
			x[n-i+1]%=mod;
		}
		m=n-m+1;
		ll s=0,ret=1,h=k;
		for(int i=m;i<=n;++i){
			s=(s+ret*inverseFactorial[i-m]%mod*x[i]%mod)%mod;
			ret=(ret*h)%mod;
			h=(h+1)%mod;
		}
		printf("%lld\n",s);
	}
	return 0;
} 
#include<bits/stdc++.h> 
#define ll long long
#define maxn 10000005
#define mod 1000000007
#define eps 1e-12
using namespace std; 
ll PowMod(ll a,ll b,ll MOD){  
    ll ret=1;  
    while(b){  
        if(b&1) ret=(ret*a)%MOD;  
        a=(a*a)%MOD;  
        b>>=1;  
    }  
    return ret;  
}  
ll fac[maxn];  
ll Get_Fact(ll p){  
    fac[0]=1;  
    for(int i=1;i<=p;i++)  //超时是因为这里的p 
        fac[i]=(fac[i-1]*i)%p;   
}  
ll Lucas(ll n,ll m,ll p){  
    ll ret=1;  
    while(n&&m){  
        ll a=n%p,b=m%p;  
        if(a<b) return 0;  
        ret=(ret*fac[a]*PowMod(fac[b]*fac[a-b]%p,p-2,p))%p;  
        n/=p;  
        m/=p;  
    }  
    return ret;  
}  
//Lucas(n,m,mod)
ll x[100005];
int main(){
	Get_Fact(mod);
	int t;
	scanf("%d",&t); 
	while(t--){	
		int n,m;
		ll k;
		scanf("%d%d%lld",&n,&m,&k);
//		cin>>n>>m>>k;
		m--;
		m=n-m-1;
		for(int i=0;i<n;++i){
			scanf("%lld",&x[n-i-1]); 
			x[n-i-1]%=mod;
		}
		ll s=0;
		for(int i=m;i<n;++i){  //本来是需要k--的,但由于123对应的111是初始状态不计在列,往后顺延抵消 
			int p=i-m;
			s=(s+Lucas(k+p-1,p,mod)*x[i]%mod)%mod;
		}
		printf("%lld\n",s);
	}
	return 0;
} 

E.https://www.codechef.com/FEB16/problems/SEATL


数据范围和子任务

• 1 ≤ 每组数据中 N × M 之和 ≤ 10^6

• 1 ≤ Ai,j ≤ 10^6

如果说上一题是惋惜,这一题就是迷茫。怎么想都会超时。

AC的代码各种千奇百怪的做法:二分,归并排序,BFS,各种神思路。。。

而我尝试用map各种超时。。。

满脑子想的是比较所有十字形里所有数的次数,也就是常规做法。除此以外我想到的都是我认为超时的做法。不得不说某大神的做法很巧妙。好题。

再想一遍...再写一遍...

#include<bits/stdc++.h> 
#define ll long long
using namespace std; 
int x[1000005];
int f[1000005];
int vis[1000005];
int row[1000005];
int col[1000005];
vector<pair<int,int> > v[1000005];
map<int,int> r;
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
	//	memset(vis,0,sizeof(vis));
		int n,m,a;
		scanf("%d%d",&n,&m);
		int c=-1;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				scanf("%d",&a);
				if(vis[a]==0)
					f[++c]=a;
				vis[a]=1;
				v[a].push_back({i,j});
			}
		}
		int maxx=0;
		for(int i=0;i<=c;++i){
			int p=f[i];    //出现过的数字
			int maxr=0,maxc=0,s1=1,s2=1; 
			for(int j=0;j<v[p].size();++j){  //遍历这个数出现过的所有位置(两个for刚好O(n*m)) 
				row[v[p][j].first]++;  //在哪一行出现过,该行次数+1 
				col[v[p][j].second]++; //在哪一列出现过,该列次数+1 
				if(row[v[p][j].first]>maxr){
					maxr=row[v[p][j].first];
					s1=1;
				}
				else if(row[v[p][j].first]==maxr)
					s1++;  //出现次数最多的行有几个 
				if(col[v[p][j].second]>maxc){
					maxc=col[v[p][j].second];
					s2=1;
				}
				else if(col[v[p][j].second]==maxc)
					s2++;  //出现次数最多的列有几个 
			}
			int s=s1*s2; //此乃画龙点睛之处 
			for(int j=0;j<v[p].size();++j){
				int a=v[p][j].first,b=v[p][j].second;
				if(row[a]==maxr&&col[b]==maxc) //这个点位于某个【行列都最多】的十字交叉上 
					s--; 
			}
			if(s>0)
				maxx=max(maxx,maxr+maxc);
			else
				maxx=max(maxx,maxr+maxc-1);
			for(int j=0;j<v[p].size();++j){  //恢复这两个数组的原貌(每次结束都归零) 
				row[v[p][j].first]--;  
				col[v[p][j].second]--; 
			}
			v[p].clear();
			vis[p]=0;
		}
		printf("%d\n",maxx);
	}
}

F.https://www.codechef.com/FEB16/problems/MTMXSUM


这题的做法我暂时不是很理解。。。

#include <bits/stdc++.h>  
using namespace std; 
#define pb push_back  
#define mp make_pair  
#define REP(i,n)for (int i=0;i<(int)(n);++i) 
typedef long long LL; 
typedef pair<int,int> PII; 
int n; 
int a[100000],b[100000]; 
const int MOD=1e9+7; 
int le[100000],ri[100000],aa[100003]={},bb[100003]={}; 
inline void modAdd(int &x,int y){  
    x+=y; 
    if (x>=MOD)x-=MOD; 
}  
inline void modSub(int &x,int y){  
    x-=y; 
    if (x<0)x+=MOD; 
}  
void solve(int *a,int *res){  
    REP(i,n){  
        le[i]=i; 
        while (le[i]>0 && a[le[i]-1]<=a[i])le[i]=le[le[i]-1]; 
    }  
    for (int i=n-1;i>=0;--i){  
        ri[i]=i; 
        while (ri[i]<n-1 && a[ri[i]+1]<a[i])ri[i]=ri[ri[i]+1]; 
    }  
    REP(i,n)if (a[i]>=MOD)a[i]-=MOD; 
    REP(i,n){  
        modAdd(res[1],a[i]); 
        modSub(res[i-le[i]+2],a[i]); 
        modSub(res[ri[i]-i+2],a[i]); 
        modAdd(res[ri[i]-le[i]+3],a[i]); 
    }  
    REP(times,2)for (int i=2;i<=n;++i){  
        modAdd(res[i],res[i-1]); 
    }  
}  
int main(){  
    scanf("%d",&n); 
    REP(i,n)scanf("%d",a+i),a[i]+=i+1; 
    REP(i,n)scanf("%d",b+i),b[i]+=i+1; 
    solve(a,aa); 
    solve(b,bb); 
    for (int i=1;i<=n;++i)printf("%d ",int((LL)aa[i]*bb[i]%MOD)); 
    printf("\n"); 
    return 0; 
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值