Codeforces Round #369 (Div. 2)
这比赛比较坑啊,看了一下A题过了7000+人,B题直接只剩2000+人。这套题的B题非常坑,很多陷阱。
A:
水题
B:
这题就是一个幻方,但是非常坑,有这么几个坑点:1.如果n==1,随便输出一个数即可,但不能是0;2.算出的答案必须是正整数
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
long long ans;
long long r[505],c[505],t1=0,t2=0;
long long sum2=0,sum1=0;
int main()
{
int n;
long long map[505][505];
scanf("%d",&n);
int row,col;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
scanf("%I64d",&map[i][j]);
if(map[i][j]==0)
{
row=i;
col=j;
}
}
if(n==1)
{
printf("1\n");
return 0;
}
for(int i=1;i<=n;i++)
sum2+=map[row][i];
if(row==1)
{
for(int i=1;i<=n;i++)
sum1+=map[2][i];
ans=sum1-sum2;
}
else
{
for(int i=1;i<=n;i++)
sum1+=map[1][i];
ans=sum1-sum2;
}
sum2+=ans;
map[row][col]=ans;
memset(r,0,sizeof(r));
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
r[i]+=map[i][j];
c[j]+=map[i][j];
}
}
for(int i=1;i<=n;i++)
t1+=map[i][i];
for(int i=1;i<=n;i++)
t2+=map[i][n-i+1];
bool flag=true;
for(int i=1;i<=n;i++)
{
if(sum2!=r[i]||sum2!=c[i])
{
flag=false;
break;
}
}
if(ans<=0)
flag=false;
if(sum2!=t1||sum2!=t2)
flag=false;
if(flag)
printf("%I64d\n",ans);
else
printf("-1\n");
return 0;
}
C:
这题的DP还是挺有想法的,可惜B题做太长时间了,基本思路就是用dp[i][j][k]表示第i个树涂颜色j,现在分段有k个,这样不断和前一个树比较即可。
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
const long long inf=1e14;
int a[105],cost[105][105];
long long dp[105][105][105];
int main()
{
int n,m,t;
int i,j,k,h;
scanf("%d%d%d",&n,&m,&t);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
scanf("%d",&cost[i][j]);
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
for(k=0;k<=n;k++)
dp[i][j][k]=inf;
if(!a[1])//对第一个树进行进行处理
{
for(i=1;i<=m;i++)
dp[1][i][1]=cost[1][i];
}
else
dp[1][a[1]][1]=0;
for(i=2;i<=n;i++)
{
if(!a[i])//此树无色
{
for(j=1;j<=m;j++)
for(k=1;k<=i;k++)
for(h=1;h<=m;h++)//这里h就代表前一棵树的颜色
{
if(j==h)
dp[i][j][k]=min(dp[i][j][k],dp[i-1][h][k]+cost[i][j]);
else
dp[i][j][k]=min(dp[i][j][k],dp[i-1][h][k-1]+cost[i][j]);
}
}
else//此树不是无色的
{
for(k=1;k<=i;k++)
for(h=1;h<=m;h++)
{
if(a[i]==h) //与前一棵树颜色相同,则取前一棵树的对应k的值</span>
dp[i][a[i]][k]=min(dp[i][a[i]][k],dp[i-1][h][k]);
else //与前一棵树颜色不同,取对应的k-1的值的所有颜色中的最小值 </span>
dp[i][a[i]][k]=min(dp[i][a[i]][k],dp[i-1][h][k-1]);
}
}
}
long long ans=inf;
for(int i=1;i<=m;i++)
ans=min(ans,dp[n][i][t]);
if(ans==inf)
printf("-1\n");
else
printf("%I64d\n",ans);
return 0;
}
D:
这题的题意就是给出一个图,其中有很多环,要求反转其中的边破坏环,问有几种组合?
就是一个dfs找环中的个数的问题,但一开始没看清题,这题简化了,图中每个节点的出度都是1,所以不存在环内指向环外以及两个环有公共部分的情况,这样就简单了。求出每个环中节点的个数为x,这样反转的组合就是2^x-2,减去2代表全部反转或都不反转。还有剩下的节点可以随便反转,最后再乘2^(n-sum)。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#define MOD 1000000007 //注意这里不能写成1e9的形式,因为1e9是double,不支持整除
using namespace std;
const int maxn=200000+5;
typedef long long ll;
int to[maxn],id[maxn],tim[maxn];
int T,sum=0;
vector<int> c;
void dfs(int u,int t)
{
id[u]=t;
tim[u]=T;
int v=to[u];
if(id[v]!=-1&&tim[v]==tim[u])//如果已经访问过并且是同一次dfs访问的,说明成环
{
c.push_back(id[u]+1-id[v]);
sum+=id[u]+1-id[v];
}
else if(id[v]==-1)
dfs(v,t+1);
}
ll mod_pow(ll a,ll b)//快速幂,注意这里a,b都是long long
{
ll ans=1;
while(b>0)
{
if(b&1) ans=ans*a%MOD;
a=a*a%MOD;
b>>=1;
}
return ans;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&to[i]);
}
memset(id,-1,sizeof(id));
T=1;
for(int i=1;i<=n;i++)
{
if(id[i]==-1)
{
dfs(i,1);
T++; //记录是第几次dfs
}
}
ll sum1=1;
for(int i=0;i<c.size();i++)
sum1=(sum1*(mod_pow(2,c[i])-2))%MOD;
printf("%I64d\n",sum1*mod_pow(2,n-sum)%MOD);
return 0;
}
E:
这题牵涉到数论中很多知识,有鸽巢原理,费马小定理求逆元,勒德让定理,gcd的相关定理等,应该算数论题中挺难的一道综合题了,反正我是做不出来,具体解释看下面的博客吧:
参考博客:点击打开链接
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#define mod 1000003
using namespace std;
typedef long long ll;
ll n, k,gyz;
ll mod_pow(ll a, ll b)//快速幂
{
ll ans = 1;
while (b > 0)
{
if (b & 1) ans = ans*a%mod;
a = a*a%mod;
b >>= 1;
}
return ans;
}
void get_gyz()//有勒德让定理求(k-1)!中公因子为2的幂的最大值
{
gyz = 0;
ll a = 2;
ll temp = (k - 1) / a;
gyz += temp;
while(temp>0)
{
a *= 2;
temp = (k - 1) / a;
gyz += temp;
}
}
ll fermat(ll a)//费马小定理求逆元
{
ll b = mod - 2;
return mod_pow(a, b);
}
int main()
{
ll fm, fz;
scanf("%I64d%I64d", &n, &k);
get_gyz();
ll x = mod_pow(2,gyz);
x = fermat(x);
x = (x + mod) % mod;
fm = mod_pow(mod_pow(2, n), k - 1)*x%mod;//求出分母/公因子的值
if (k - 1 >= mod)//鸽巢原理,分子%mod一定为0
fz = 0;
else
{
fz = 1;
ll temp = mod_pow(2, n);
ll t;
for(int i=1;i<k;i++)
{
t = temp - i < 0 ? temp - i + mod : temp - i;//如果是负数就+mod
fz = fz*t%mod;
}
fz = fz*x%mod;
}
int flag = 0;
ll temp = 1;
for (int i = 1; i <= n&&i <= 63; i++)//若发生k>2^n的情况,即人数大于天数,则概率为100%
{ //注意这里比较k因为是long long型,所以n>=64就不用比较了,肯定是2^n大了
temp *= 2; //还有这里不能使用上面的快速幂函数,因为上面已经%mod了
if (k <= temp)
{
flag = 1;
}
}
printf("%I64d\n", temp);
if (flag)
printf("%I64d %I64d\n", (fm - fz + mod) % mod, fm);//由于求出的分数是生日相异的情况,则结果为1-分数,但分子可能出现负数的情况,所以加一个mod再取模
else
printf("1 1\n");
return 0;
}