A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5670
官方题解:
红、绿、蓝分别表示0、1、2,每次操作就相当于+1,原问题就转化为求n的三进制
表示的最低的m位,即求 n mod 3m的三进制表示。
复杂度 O(m)
我的理解:简单递推 不过读懂题意对我来说有点难
#include<cstdio>
#include<cstring>
using namespace std;
#define ll __int64
char ch[5]="RGB";
char s[31];
int main()
{
int T,m,i;
ll n;
scanf("%d",&T);
while(T--)
{
memset(s,0,sizeof(s));
scanf("%d%I64d",&m,&n);
for(i=m-1;i>=0;i--)
{
s[i]=ch[n%3];
n/=3;
}
printf("%s\n",s);
}
}
官方题解:
对于交换行、交换列的操作,分别记录当前状态下每一行、每一列是原始数组的哪一行、哪一列即可。
对每一行、每一列加一个数的操作,也可以两个数组分别记录。注意当交换行、列的同时,也要交换增量数组。
输出时通过索引找到原矩阵中的值,再加上行、列的增量。
复杂度O(q+mn)
我的理解:简单题,用row col数组标记下当前矩阵的ij位置是原先的哪一行那一列即可,每次都在初始矩阵上进行加减操作
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1005;
int num[1005][1005],row[N],line[N];
int i,j,n,m,q,tmp,T,a,b,c;
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&m,&q);
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
scanf("%d",&num[i][j]);
}
for(i=1;i<=n;i++) row[i]=i;
for(i=1;i<=m;i++) line[i]=i;
while(q--)
{
scanf("%d%d%d",&a,&b,&c);
if(a==1)
{
int tmp=row[b];
row[b]=row[c];
row[c]=tmp;
}
else if(a==2)
{
int tmp=line[b];
line[b]=line[c];
line[c]=tmp;
}
else if(a==3)
{
for(i=1;i<=m;i++)
num[row[b]][i]+=c;
}
else
{
for(i=1;i<=n;i++)
num[i][line[b]]+=c;
}
}
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
if(j==1)printf("%d",num[row[i]][line[j]]);
else printf(" %d",num[row[i]][line[j]]);
}
printf("\n");
}
}
return 0;
}
官方题解:
有一个明显的性质:如果子串(i,j)包含了至少k个不同的字符,那么子串(i,k),(j<k<length)也包含了至少k个不同字符。
因此对于每一个左边界,只要找到最小的满足条件的右边界,就能在O(1)时间内统计完所有以这个左边界开始的符合条件的子串。
寻找这个右边界,是经典的追赶法(尺取法,双指针法)问题。维护两个指针(数组下标),轮流更新左右边界,同时累加答案即可。复杂度 O(length(S))。
我的理解:简单爆搜,从左往右搜,用cnt记录当前不同的字符个数,若cnt<k,while循环知道cnt=k为止,如果无法满足此条件说明不在存在该种字符串了,跳出循环输出答案
如果cnt==k,答案增加l-j,其中l为总长度,j为当前字符串尾的长度,i和j记录的是当前字符串的头和尾
#include<cstdio>
#include<cstring>
#define ll __int64
using namespace std;
char s[1000005];
ll ans;
int vis[26];
int main()
{
int T,l,k;
scanf("%d",&T);
while(T--)
{
memset(vis,0,sizeof(vis));
scanf("%s",s);
l=strlen(s);
scanf("%d",&k);
ans=0;
int i=0,j=0,cnt=1;
vis[s[0]-'a']++;
while(i<l)
{
while(cnt<k)
{
j++;
if(j==l)break;
if(vis[s[j]-'a']==0)cnt++;
vis[s[j]-'a']++;
}
if(cnt<k)break;
ans+=(l-j);
vis[s[i]-'a']--;
if(vis[s[i]-'a']==0)cnt--;
i++;
}
printf("%I64d\n",ans);
}
}
官方题解:
记路径长度为n,那么机器人最多向右走⌊2n⌋步并向左走⌊2n⌋步。
Ans(n)=∑i=0⌊2n⌋Cn2i Catalan(i) 其中Catalan(n)表示第n个卡特兰数。
卡特兰数定义:Catalan(n)=n+1C2nn
递推公式Catalan(n)=n+14n−2 Catalan(n−1)
基于n的取值范围,此题可以预处理出1,000,001以内的乘法逆元、卡特兰数。
每次询问,都可以递推组合数,或者提前一次性预处理好阶乘和阶乘的逆元得到组合数;累加组合数与相应卡特兰数的乘积,得到答案。
事实上,Ans(n)是第n个默慈金数,还有更高效的递推公式:
Mn+1=Mn+∑i=0n−1MiMn−1−i=n+3(2n+3)Mn+3nMn−1。
我的思考:get新知识 1.卡特兰数 2.线性求逆元 3.默慈金数 此题为默慈金数的应用,也是卡特兰数的经典应用之一,基本上就是裸题,两者效率相差较大
#include<cstdio>//默慈金数
#define ll __int64
using namespace std;
const int mod=1e9+7;
const int N=1000009;
ll dp[N],m[N];
int main()
{
int T,n;
m[1]=1;
for(int i=2;i<=N-9;i++)
m[i]=((mod-mod/i)*m[mod%i])%mod;
dp[1]=1;dp[2]=2;dp[3]=4;
for(ll int i=4;i<=N-9;i++)
{
dp[i]=((dp[i-1]*(2*(i-1)+3)+3*(i-1)*dp[i-2])%mod*m[i-1+3])%mod;
}
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
printf("%d\n",dp[n]);
}
return 0;
}
#include<cstdio>//卡特兰数
#include<cstring>
using namespace std;
#define ll __int64
const int N = 1000100;
const int mod = 1000000000 + 7;
ll inv[N],h[N],c[N];
int T,n,m;
void init()
{
inv[1]=1;
for(int i=2;i<N;i++)
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}
int main()
{
init();
scanf("%d",&T);
h[1]=h[0]=1;
for(int i=2;i<N;i++)
h[i]=h[i-1]*(4*i-2)%mod*inv[i+1]%mod;
while(T--)
{
scanf("%d",&n);
ll ans=1;
c[0]=1;
for(int i=1;i<=n;++i)
c[i]=c[i-1]*(n-i+1)%mod*inv[i]%mod;
for(int i=1;i<=n/2;++i)
{
int k=n-(i<<1);
ans = (ans+h[i]*c[k])%mod;
}
printf("%I64d\n", ans);
}
return 0;
}