这周打了几场比赛,补了补题,到现在来说自己能做的还是只有思维题,算法题虽然学了但现在也只是能应付个模板题,补完题又去学了数论的一些知识,鸽巢原理,RMQ算法和容斥原理,这两个原理看起来挺简单的,但一碰见有关的题难度却在意料之外,看来熟练运用是有难度的,近期因为是在学数学主要是在vjudge做题,洛谷的题想等到数论看一遍之后再去刷
牛客 Mio visits ACGN Exhibition
看数据范围就知道搜索必不可行,看的题解用的是dp,dp[i][j][x]表示走到(i,j)时,拥有x个0,i+j-1-x个1,可以用滚动数组优化掉i这一维,则有状态方程
if(mp[i][j]) dp[j][x]=(dp[j-1][x]+dp[j][x])%mod;
else {if(x) dp[j][x]=(dp[j-1][x-1]+dp[j][x-1])%mod;
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const int inf=0x3f3f3f3f;
ll n,m,p,q,dp[505][1100],mp[1100][1100];
int main(){
//freopen("in.txt","r",stdin);
scanf("%lld%lld%lld%lld",&n,&m,&p,&q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lld",&mp[i][j]);
(!mp[1][1])?dp[1][1]=1:dp[1][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i==1&&j==1) continue;
for(int x=i+j-1;x>=0;x--){
if(mp[i][j]) dp[j][x]=(dp[j-1][x]+dp[j][x])%mod;
else {if(x) dp[j][x]=(dp[j-1][x-1]+dp[j][x-1])%mod;}
}
if(!mp[i][j]) dp[j][0]=0;//怕重复计算
//当a[i][j]=0时,由dp[j][0]转移到f[j][1]已经将a[i][j]=0的贡献加进去了,若不将dp[j][0]清空,
//转移到第i+1行,dp[j][0]的会被重复加
}
}
ll ans=0;
for(int i=p;i<=n+m-1-q;i++)
ans=(ans+dp[m][i])%mod;
printf("%d\n",ans);
return 0;
}
poj 2356
可以得出结论,n个数一定会有若干个连续数的和是n的倍数
假设有n个数,设sum[i]为前i个数的和,会有两个情况(1):至少有1个sum[i]是n的倍数
(2):没有一个sum[i]是n的倍数
如果是情况1,那么结论成立;如果是情况2,那么我们可以想一下,每个sum[i]都有sum[i]%n!=0,就说明取余的结果有n种,但取余的范围是0-n-1,所以必有两个sum[i],sum[j],他们取余n结果是相同的,那就可以说明他们的差一定会是n的倍数
Find a multiple (POJ-2356)(抽屉原理)_菜鸡的博客-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#define ll long long
using namespace std;
const int maxx=10010;
const int MOD=998244353;
const int inf=0x3f3f3f3f;
int a[maxx];
int sum[maxx],mod[maxx];
int main(){
//freopen("in.txt","r",stdin);
int n;
while(~scanf("%d",&n)){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i]=a[i]+sum[i-1];
}
for(int i=1;i<=n;i++){
if(sum[i]%n==0){
printf("%d\n",i);
for(int j=1;j<=i;j++)
printf("%d\n",a[j]);
break;
}
if(mod[sum[i]%n]!=0){//说明有和他相同取余的i
printf("%d\n",i-mod[sum[i]%n]);
for(int j=mod[sum[i]%n]+1;j<=i;j++)
printf("%d\n",a[j]);
break;
}
mod[sum[i]%n]=i;//记录取余情况
}
}
return 0;
}
hdu 3183
关于RMQ算法和鸽巢原理的题,但我并没有发现哪里用到了鸽巢原理。。。难不成是删数的时候用到?可能是变了一下我就不懂了,可能是这个地方:删m个数就代表保留n-m个数,要使第一位最小,那么只能从1--m+1个数中取,假设取到了第k个,那么第二个数就是k+1到m+2中取,以此类推,最后判断前导0;
为此学了下RMQ算法。。。
HDU-3183——A Magic Lamp(RMQ问题+鸽巢原理)_shengtao96的专栏-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
ll mod=1e9+7;
char str[1005],ans[1005];
int dp[1005][10];
int minn(int i,int j){
return str[i]<=str[j]?i:j;
}
void rmq(int n){
memset(dp,inf,sizeof(dp));
for(int i=0;i<n;i++)
dp[i][0]=i;//初始化,表示第i位往后2^j个数的最小值
for(int j=1;j<10;j++)
for(int i=0;i<n&&i+(1<<j)-1<n;i++)
dp[i][j]=minn(dp[i][j-1],dp[i+(1<<j-1)][j-1]);//每次都分成两半查找
}
int query(int l,int r){//查找整个区间的最小值
int k=log2(r-l+1);
return minn(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main(){
//freopen("in.txt","r",stdin);
int m;
while(~scanf("%s %d",str,&m)){
int n=strlen(str);
rmq(n);
int ld=-1,M=m;
int len=n-m;
for(int i=0;i<len;i++,M++){
ld=query(ld+1,M);
ans[i]=str[ld];
}
int kk=0;
while(ans[kk]=='0'&&kk<len)kk++;
if(kk==len){
printf("0\n");
continue;
}
for(;kk<len;kk++)
printf("%c",ans[kk]);
printf("\n");
}
return 0;
}
codeforces C Array Elimination
关于位运算的题目
Codeforces-1601 A: Array Elimination_Sherlock_Holmewei的博客-CSDN博客
#include<bits/stdc++.h>
using namespace std;
int t,n,a[200005],bit[33];
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&t);
while(t--){
for(int i=0;i<32;i++) bit[i]=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
for(int j=0;j<32;j++){
bit[j]+=(1&(a[i]>>j));
}
}
int k=bit[0];
for(int i=1;i<32;i++) k=__gcd(k,bit[i]);
if(k==0){
for(int i=1;i<=n;i++) cout<<i<<" ";
}
else{
for(int i=1;i<=k;i++){
if(k%i==0) cout<<i<<" ";
}
}
cout<<endl;
}
return 0;
}
hdu 2841
求m个数中与i互质的个数,容斥原理的题目,虽然有公式:n/2+n/3+n/5-n/(2*3)-n/(2*5)-n/(3*5)+n/(2*3*5)(奇数加,偶数减),但还是不明白其中的道理,先写上之后再问问别人;
容斥原理模板(二进制表示)_MInNrz的Love&Share-CSDN博客
Visible Trees (HDU-2841)(容斥原理)_菜鸡的博客-CSDN博客 题解
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
int fac[100000];
int div(int n){//找n的质因子及其个数
int cnt=0;
for(int i=2;i*i<=n;i++){
if(n%i==0){
fac[cnt++]=i;
while(n%i==0) n/=i;
}
}
if(n>1) fac[cnt++]=n;
return cnt;
}
int solve(int n,int cnt){
int ans=0;
for(int i=1;i<(1<<cnt);i++){//质因子集合子集的个数一共(2^cnt)-1个
int ones=0,mul=1; //不算空集
for(int j=0;j<cnt;j++){
if(i&(1<<j)){//如果i的第j位有1,说明j在这个子集内
ones++;
mul*=fac[j];
}
}//容斥原理公式是:n/2+n/3+n/5-n/(2*3)-n/(2*5)-n/(3*5)+n/(2*3*5)
//说白了就是奇数就加,偶数就减
if(ones&1) ans+=n/mul;
else ans-=n/mul;
}
return n-ans;
}
int main(){
//freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
while(t--){
int n,m;
ll ans=0;
cin>>n>>m;
for(int i=1;i<=n;i++){
int cnt=div(i);
ans+=solve(m,cnt);//这里计算的是1到m这m个数中与i互质的个数
//枚举行数,求出第i行的m个数中与i互质的个数,最后全加起来就是答案
}
cout<<ans<<endl;
}
return 0;
}
hdu 4135
容斥原理,找L/G质因子出现的次数然后乘以6,最后全部乘起来就是答案,但没大理解到底为什么要这样做
这篇讲的思路hdu4497 (唯一分解定理)_sole’s blog-CSDN博客
看的这篇的代码HDU 4497 GCD and LCM (数论&组合数学)_AC,∑ndless-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
int main(){
int t;
//freopen("in.txt","r",stdin);
ll m,n,ans,i,cnt;
scanf("%d",&t);
while(t--){
scanf("%lld%lld",&m,&n);
if(n%m) cout<<0<<endl;//n%m!=0直接不符合条件
else{
n/=m;
ans=1;
for(int i=2;i*i<=n;i+=2){
if(n%i==0){
cnt=0;
while(n%i==0){
n/=i;
++cnt;//统计该质因子出现的次数
}
ans*=6*cnt;//有6种组合情况所以×6
}
if(i==2)
--i;//让后面的i都变为素数
}
if(n>1) ans*=6;
printf("%lld\n",ans);
}
}
return 0;
}
hdu 5155
依次求出至少有x列一个钻石也没有的情况,最后利用容斥原理做
HDU-5155-Harry And Magic Box(容斥)_娃娃酱斯密酱的博客-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
const int mod=1000000007;
ll c[55][55];
int n,m;
void init(){//求的是组合数
c[0][0]=1;
for(int i=1;i<=50;i++){
c[i][0]=c[i][i]=1;
}
for(int i=2;i<=50;i++)
for(int j=1;j<i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
ll npow(ll a,int n){
ll res=1;
while(n){
if(n&1) res=res*a%mod;
a=a*a%mod;
n>>=1;
}
return res;
}
int main(){
//freopen("in.txt","r",stdin);
init();
while(~scanf("%d%d",&n,&m)){
ll ans=0;
for(int i=0;i<=m;i++){
//至少有i列一个钻石也没有的情况
ll t=c[m][i]*npow(npow(2,m-i)-1,n)%mod;
if(i&1) ans=(ans-t+mod)%mod;
else ans=(ans+t)%mod;
}
cout<<ans%mod<<endl;
}
return 0;
}
codeforces J Prime Game
普通的打表暴力必会超时,这是个偏思维的数学题,可以算出每个素数对整个序列的贡献度,最后全部加起来,题解讲的详细多了。。。
J Prime Game(数学,思维,拆分素因子)_Plus Ultra!-CSDN博客
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
int n;
int a[1000010];
vector<int>v[1000010];
int main(){
// freopen("in.txt","r",stdin);
for(int i=2;i<1000010;i++)
v[i].push_back(0);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
int m=a[i];
for(int j=2;j*j<=m;j++){
if(m%j==0){
v[j].push_back(i);
while(m%j==0) m/=j;
}
}
if(m>1) v[m].push_back(i);
}
ll ans=0;
for(ll i=2;i<1000010;i++)
for(ll j=1;j<v[i].size();j++)
ans+=(ll)(v[i][j]-v[i][j-1])*(n-v[i][j]+1);
//v[i][j]代表第j个包含素数i的数的位置
cout<<ans<<endl;
return 0;
}